<<<<<<< HEAD ======= >>>>>>> Alejandra Eliminación de ruido en imágenes con wavelets.

1 Introducción

En el campo del análisis de señales, uno de los problemas más relevantes es la eliminación de ruido en imágenes digitales. Las señales, que representan variaciones de magnitudes en el espacio y/o tiempo, a menudo están contaminadas por ruido debido a interferencias en los procesos de captura o transmisión. Este ruido, que puede existir de formas muy diversas como ruido gaussiano, artefactos asociados a frecuencias altas o bajas, o efectos multiplicativos, complica la extracción de características significativas.

El uso de wavelets es una herramienta clave para abordar este problema, el cual permite descomponer señales en sus componentes espaciales y frecuenciales de forma eficiente. Este método ofrece la posibilidad de adaptarse a las características particulares de cada tipo de ruido.

En este trabajo se propone explorar la capacidad de los wavelets para la eliminación de ruido en imágenes digitales. Para ello, se generarán y analizarán diferentes tipos de ruido artificial, evaluando su complejidad para su atenuación y determinando los parámetros más eficaces de los wavelets.

2 Fundamento teórico: eliminación de ruido con wavelets

Los wavelets son funciones matemáticas que permiten descomponer una señal en componentes de diferentes escalas, lo que resulta útil para identificar y procesar características específicas. Este enfoque es especialmente relevante en la eliminación de ruido, donde las frecuencias indeseadas pueden ser separadas y reducidas sin afectar significativamente las características principales de la señal original. A diferencia de la Transformada de Fourier, que opera globalmente y no ofrece información sobre la localización temporal de los eventos, los wavelets permiten un análisis localizado, facilitando la identificación de patrones y anomalías en los datos.

El proceso de reducción de ruido con wavelets generalmente incluye tres etapas principales: la descomposición de la señal utilizando una wavelet madre, la modificación de los coeficientes wavelet mediante técnicas de umbral, y la reconstrucción de la señal. La selección de la wavelet madre adecuada y los parámetros de umbral son aspectos críticos que deben adaptarse a las características específicas del ruido y la señal.

Además, los wavelets ofrecen un enfoque multi-resolución, permitiendo una representación detallada de los componentes de alta frecuencia, asociados frecuentemente con el ruido, mientras preservan las estructuras globales de baja frecuencia. Esta característica hace que los wavelets sean especialmente útiles en aplicaciones donde la precisión y la integridad de los datos son esenciales, como en imágenes médicas, procesamiento de audio o análisis de datos científicos.

En este trabajo, se emplearán wavelets como herramienta principal para eliminar diferentes tipos de ruido sintético en imágenes.

3 Funciones de programacion empleadas

4 Desarrollo y resultados

5 Fundamento teórico: eliminación de ruido con wavelets

6 Funciones de programacion empleadas

Eliminación de ruido con el algoritmo de Mallat

Empleamos la funicón imwd() del paquete wavethresh. Está función realiza una transformada discreta wavelet de acuerdo con el algoritmo de Mallat.

El argumento image de la función debe ser una matriz cuadrada cuya dimensión sea potencia de dos.filter.number elige la suavidad de la wavelet a emplear, siendo por defecto 10. family indica la familia de wavelets a emplear (“DaubExPhase” ó “DaubLeAsymm”).Para tratar las fronteras, mantendremos el parámetro bc = "periodic" por defecto.

Para la eliminación de ruido aplicaremos la función threshold() al objeto que devuelve la función imwd().

levels es el número de niveles a los cuáles deseamos aplicar un umbral, mientras que type indica si queremos un umbral más suave (“soft”) ó más fuerte (“hard”). El parámetro policy selecciona la técnica para elegir umbral. by.level = FALSE significa que se aplica un umbral global a todos los niveles indicados, mientras que by.level = TRUE calcular threshold para cada nivel por seaparado. El parámetro value, el valor del umbral, se usará si elegimos técnicas manuales en policy. Si queremos obtener el valor umbral aplicado, pondremos return.threshold = TRUE. Finalmente, dejaremos el parámetro compression = TRUE por defecto, para obterner un objeto más pequeño.

Durante el desarrollo del trabajo variaremos estos parámetros para observar su efecto en la eliminación de diversos tipos de ruido y encontar aquellos que generen un mejor resultado en cada caso. Una vez realizado el thresholding, emplearemos la función imwr para realizar la transformda wavelet inversa y poder visualizar la imagen tras la eliminación de ruido.

7 Desarrollo y resultados

Antes de comenzar, instalamos y/o cargamos todos los paquetes requeridos.

if (!require("pacman")) install.packages("pacman")
library(pacman)
p_load(imager, wavethresh, ggplot2, dplyr, SpatialPack, waveslim, EBImage, stringr, jpeg, abind, magick)

Comenzamos cargando y visualizando las fotografías a emplear.

images_path <- list.files("./fotos", full.names = TRUE)

nombres_images <- str_remove_all(string = str_remove_all(string = images_path, pattern = "./fotos/"), pattern = "\\.JPG|\\.jpg")

images <- lapply(images_path, readJPEG) # Cargamos las imágenes
names(images) <- nombres_images
# Rotamos algunas de las fotos para una visualización más uniforme
fotos_a_girar <- c("1", "2", "3", "4")
images_rotadas <- lapply(images[fotos_a_girar], aperm, perm = c(2, 1, 3))


for (i in fotos_a_girar) {
  images_rotadas[[i]] < images_rotadas[[i]][dim(images_rotadas[[i]])[1]:1, , ]
}

images[fotos_a_girar] <- images_rotadas
rm(images_rotadas)
rm(fotos_a_girar)
# Visualizamos las imagenes originales (comento para que no tarde en ejecutar)

#par(mfrow = c(2, 3), mar = c(1, 1, 1, 1))

# for (img in nombres_images) {
#   display(Image(images[[img]], colormode = "Color"), method = "r")
#   title(paste('Imagen', img))
# }

7.1 Inclusión de ruido sintético en las imágenes

<<<<<<< HEAD ======= >>>>>>> Alejandra
# Definición de tipos de ruido

NOISE_TYPES <- list(
  gaussian = list(
    generator = function(channel, params) {
      # Desviación estándar del ruido con un valor predeterminado
      noise_std_dev <- params$std_dev %||% 0.5

      # Generación de ruido gaussiano
      noise <- array(
        rnorm(length(channel), mean = 0, sd = noise_std_dev),
        dim = dim(channel)
      )

      # Asegurar que los valores estén entre 0 y 1
      pmax(0, pmin(1, channel + noise))
    }
  ),
  sinusoidal_high = list(
    generator = function(channel, params) {
      # Frecuencia y amplitud del ruido sinusoidal de alta frecuencia
      frequency <- params$frequency %||% 25
      amplitude <- params$amplitude %||% 0.2

      # Generación de ruido sinusoidal
      height <- dim(channel)[1]
      width <- dim(channel)[2]
      x <- seq(0, 2 * pi, length.out = width)
      y <- seq(0, 2 * pi, length.out = height)
      noise_grid <- outer(sin(x * frequency), sin(y * frequency))

      # Aplicar el ruido
      noise <- array(noise_grid * amplitude, dim = dim(channel))
      pmax(0, pmin(1, channel + noise))
    }
  ),
  sinusoidal_low = list(
    generator = function(channel, params) {
      # Frecuencia y amplitud del ruido sinusoidal de baja frecuencia
      frequency <- params$frequency %||% 2
      amplitude <- params$amplitude %||% 0.2

      # Generación de ruido sinusoidal
      height <- dim(channel)[1]
      width <- dim(channel)[2]
      x <- seq(0, 2 * pi, length.out = width)
      y <- seq(0, 2 * pi, length.out = height)
      noise_grid <- outer(sin(x * frequency), sin(y * frequency))

      # Aplicar el ruido
      noise <- array(noise_grid * amplitude, dim = dim(channel))
      pmax(0, pmin(1, channel + noise))
    }
  ),
  salt_pepper = list(
    generator = function(channel, params) {
      # Proporción de píxeles afectados por el ruido de sal y pimienta
      epsilon <- params$epsilon %||% 0.2

      # Generación de ruido
      noise <- matrix(sample(c(0, 1, NA), length(channel), replace = TRUE, prob = c(epsilon / 2, epsilon / 2, 1 - epsilon)),
        nrow = dim(channel)[1], ncol = dim(channel)[2]
      )
      channel[!is.na(noise)] <- noise[!is.na(noise)]
      channel
    }
  ),
  gamma = list(
    generator = function(channel, params) {
      # Ruido multiplicativo gamma con parámetro de dispersión
      looks <- params$looks %||% 2
      noise <- array(rgamma(length(channel), shape = looks, scale = 1 / looks), dim = dim(channel))
      pmax(0, pmin(1, channel * noise))
    }
  ),
  uniform_multiplicative = list(
    generator = function(channel, params) {
      # Ruido multiplicativo uniforme
      looks <- params$looks %||% 2
      noise_channel <- SpatialPack::imnoise(
        img = channel,
        type = "speckle",
        looks = looks
      )
      pmax(0, pmin(1, noise_channel))
    }
  )
<<<<<<< HEAD
)
# Función para añadir ruido a una imagen
add_noise_to_image <- function(image_name, noise_type, noise_params = list(), plot = FALSE) {
=======
)

# Función para añadir ruido a una imagen
add_noise_to_image <- function(image_name, noise_type, noise_params = list(),plot=FALSE) {
>>>>>>> Alejandra
  # Verificar si la imagen existe en la lista
  if (!image_name %in% names(images)) {
    stop("La imagen con este nombre no se encuentra en la lista 'images'")
  }

  # Verificar el tipo de ruido
  if (!noise_type %in% names(NOISE_TYPES)) {
    stop(
      "El tipo de ruido es desconocido. Tipos disponibles: ",
      paste(names(NOISE_TYPES), collapse = ", ")
    )
  }

  # Obtener la imagen original de la lista
  original_image <- images[[image_name]]

  # Convertir la imagen a un array si es necesario
  image_array <- as.array(original_image)

  # Aplicar ruido a cada canal
  noisy_channels <- lapply(1:3, function(i) {
    channel <- image_array[, , i]
    NOISE_TYPES[[noise_type]]$generator(channel, noise_params)
  })

  # Crear la imagen con ruido
  noisy_image_array <- array(
    unlist(noisy_channels),
    dim = dim(image_array)
  )

<<<<<<< HEAD
  # Visualizar si se ha indicado
=======
 # Visualizar si se ha indicado
>>>>>>> Alejandra
  if (plot == TRUE){
  layout(matrix(1:2, 1, 2))
  plot(Image(original_image, colormode = "Color"))
  title("Original")
  plot(Image((noisy_image_array), colormode = "Color"))
  title(paste("Ruido:", noise_type))}
  
  return(noisy_image_array)
}
<<<<<<< HEAD


# Aplicar los diferentes tipos de ruido a cada imagen
add_noise_to_image("1", "gaussian", list(std_dev = 0.3))
 [ reached getOption("max.print") -- omitted 3 matrix slice(s) ]
add_noise_to_image("2", "sinusoidal_high", list(frequency = 25, amplitude = 0.2))
 [ reached getOption("max.print") -- omitted 3 matrix slice(s) ]
add_noise_to_image("3", "sinusoidal_low", list(frequency = 2, amplitude = 0.2))
 [ reached getOption("max.print") -- omitted 3 matrix slice(s) ]
add_noise_to_image("4", "salt_pepper", list(epsilon = 0.1))
 [ reached getOption("max.print") -- omitted 3 matrix slice(s) ]
add_noise_to_image("5", "gamma", list(looks = 2))
 [ reached getOption("max.print") -- omitted 3 matrix slice(s) ]
add_noise_to_image("5", "uniform_multiplicative", list(looks = 2))
 [ reached getOption("max.print") -- omitted 3 matrix slice(s) ]
# Representación de las dos familias de wavelets empleadas por la función imwd.

# Generación de un filtro con los coeficientes correspondientes de cada wavelet
wave_coeffs <- filter.select(filter.number = 10, family = "DaubLeAsymm")
wave_coeffs_2 <- filter.select(filter.number = 10, family = "DaubExPhase")

# Eje temporal
x <- seq_along(wave_coeffs$H)

# # Visualización de las wavelets
par(mfrow = c(1, 2))

plot(x, wave_coeffs$H, type = "l", lwd = 2,
     main = "Wavelet DaubLeAsymm",
     xlab = "x", ylab = "Amplitud")
points(x, wave_coeffs$H, pch = 19)

plot(x, wave_coeffs_2$H, type = "l", lwd = 2,
     main = "Wavelet DaubExPhase",
     xlab = "x", ylab = "Amplitud")
points(x, wave_coeffs_2$H, pch = 19)



rm(wave_coeffs)
rm(wave_coeffs_2)
rm(x)
=======

# Aplicar los diferentes tipos de ruido a cada imagen
#add_noise_to_image("1", "gaussian", list(std_dev = 0.3))
#add_noise_to_image("2", "sinusoidal_high", list(frequency = 25, amplitude = 0.2))
#add_noise_to_image("3", "sinusoidal_low", list(frequency = 2, amplitude = 0.2))
#add_noise_to_image("4", "salt_pepper", list(epsilon = 0.1))
#add_noise_to_image("5", "gamma", list(looks = 2))
#add_noise_to_image("5", "uniform_multiplicative", list(looks = 2))
>>>>>>> Alejandra
<<<<<<< HEAD

7.2 Función imwd

El primer método que emplearemos para la eliminación de ruido es el algoritmo de Mallat a través de la función imwd. Antes de poder aplicar esta función, necessitames realizar un pre-procesamiento a las imágenes, ya que este algoritmo necesita una matriz cuadrada cuyas dimensiones sean potencia de dos. Se han escogido 2 maneras distintas para obtener imágenes con el tamaño adecuado. Por un lado, redimensionaremos las imágenes con la función resize, lo que podría conllevar problemas de distorsión si las imágenes estaban lejos de tener dimensiones cuadradas. Por ello, también vamos a emplear otra técnica y rellenaremos las matrices de las imágnes con valores de 0 hasta alcanzar las dimensiones adecuadas. Observamos los problemas que surguen en cada caso y trataremos de solucionarlos de distintas maneras.

7.2.1 Redimensionando las imágenes

Como ya se ha visto para poder aplicar la función imwd()es necesario partir de una matriz cuadrada cuyas dimensiones sean potencia de dos. Por ello, en primer lugar creamos una función resize_imwd() tal que dada una foto busca la submatriz cuadrada y potencia de dos más grande posible y a continuacón redimensiona la imagen a dicha submatriz cuadrada.

# Pre-procesamiento al aplicado de función imwd (algoritmo de Mallat).
# Redimensionamiento de la imagen 

resize_imwd<- function(foto){
  
  img <- as.cimg(foto) # Pasar a formato Imager para aplicar función resize
   
  # Dimensiones de la foto
  dim_foto <- dim(foto)
  filas <- dim_foto[1]
  columnas <- dim_foto[2]
  
  lado_minimo <- min(filas, columnas)  # Tamaño submatriz cuadrada mas grande
  lado_potencia2 <- 2^floor(log2(lado_minimo)) # Tamaño submatriz cuadrada potencia de dos mas grande
  
  foto_resized <-resize(foto, w = lado_potencia2, h = lado_potencia2) # Redimensionado de la imagen

return(foto_resized)
}

Por otro lado, creamos una función para el post-procesamiento de las imágenes tras la eliminición de ruido. Queremos devolverlas a su tamaño original con el objetivo de comparar con las imágenes iniciales.

# Post-procesamiento de la imagen:
# Redimensionamiento de la imagen a su tamaño original.

resize_imwd_to_original<- function(image_redimensionada, nombre_foto){
  
  #img <- as.cimg(image_redimensionada)
  foto <- images[[nombre_foto]]
  
  # Dimensiones de la foto
  dim_foto <- dim(foto)
  filas <- dim_foto[2]
  columnas <- dim_foto[1]
  
  # Redimensionado de la imagen
  foto_resized <-EBImage::resize(image_redimensionada, w = columnas, h = filas) 
 

return(foto_resized)
}

Comenzamos generando una función procesar_imagen_wavelet con parámetros foto, tipo y policy. Esta función realiza en primer lugar la transformada wavelet a cada uno de los tres canales de una imagen. A continuación, se realiza el thresholding con la función threshold, pudiendo variar de el tipo de “hard” a “soft” y el parámetro policy (modificando adecuadamente los parámetros necesarios en la función threshold en este último caso). Una vez realizada la eliminación de ruido, se aplica la trasnformada wavelet inversa imwr para por último reconstruir la imagen a partir de los tres canales.

procesar_imagen_wavelet <- function(foto, tipo = "hard", policy = "universal") {
  # 1. Realizamos la transformada wavelet a cada canal
  lwd <- lapply(1:3, function(canal) {
    imwd(foto[,,canal])  
  })
  
  # 2. Aplicamos el umbral a los coeficientes de la transformada wavelet
  lwd_threshold <- lapply(lwd, function(canal_wd) {
    niveles <- canal_wd$nlevels
    wavethresh::threshold(canal_wd, levels = 3:(niveles-1), type = tipo, policy = policy,by_level=TRUE,compression=FALSE)
  })
  # 3. Aplicamos la transformada wavelet inversa a cada canal umbralizado
  ilwd <- lapply(lwd_threshold, function(canal_umbralizado) {
    wavethresh::imwr(canal_umbralizado)  # Transformada wavelet inversa
  })
  
  # 4. Reconstruir la imagen combinando los tres canales procesados
  imagen_reconstruida <- abind::abind(ilwd[[1]], ilwd[[2]], ilwd[[3]], along = 3)
    imagen <- Image(imagen_reconstruida, colormode = 'Color')
  
  return(imagen)
}

Para comenzar el análisis, vamos a emplear dos imágenes muy parecidas (imágenes 4 y 5). Una de ellas tiene muy alta resolución mientras que la segunda cuenta con una calidad mucho menor. El objetivo es determinar si la resolución de la imagen afecta a la hora de eliminar ruido de esta. Vamos a probar con el primer tipo de ruido, ruido gaussiano.

# Añadimos ruido gaussiano a las imágenes con sd = 0.3
image_4_gaussian_noise <- add_noise_to_image("4", "gaussian", list(std_dev = 0.3))
image_5_gaussian_noise <- add_noise_to_image("5", "gaussian", list(std_dev = 0.3))

# Hacemos una lista con las imágenes con ruido a redimensionar
images_for_imwd_cut_1 <- list(image_4_gaussian_noise, image_5_gaussian_noise)
names(images_for_imwd_cut_1) <- c('4 Noise: gaussian', '5 Noise: gaussian')

# Eliminamos variables innecesarias
rm(image_4_gaussian_noise)
rm(image_5_gaussian_noise)

Aplicamos la función resize_imwd creada anteriormente para obtener una matriz con las dimensiones necesarias para aplicar la transformada wavelet.

images_recortadas <- lapply(images_for_imwd_cut_1, resize_imwd)
Warning: Assuming third dimension corresponds to colourWarning: Assuming third dimension corresponds to colour

Una vez tenemos las imágenes con ruido generadas y redimensionadas adecuadamente, podemos aplicar la función imwd a cada uno de los tres canales (R, G y B). Empleamos la función procesar_imagen_wavelet que devuelve las imágenes reconstruidas después del thresholding. Para ruido gaussiano y para comenzar, vamos a elegir los parámetros por defecto de la función para el thresholding.

images_sin_ruido_gaussiano <- lapply(images_recortadas, procesar_imagen_wavelet)

Finalmente, visualizamos los resultados. Primeramente, observamos las imágenes redimensionadas con ruido y la imagen obtenida tras el uso del método de thresholding para la eliminación de este. Observamos una principal diferencia entre ambas: la foto que contaba con menor resolución presenta también el peor resultado. Aunque el ruido haya sido eliminadom, sus bordes están más difuminados y tiene muy baja calidad.

par(mfrow = c(2, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 4 con ruido')
display(Image(images_sin_ruido_gaussiano[[1]], colormode = 'Color'), method='r')
title('Imagen 4 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 5 con ruido')
display(Image(images_sin_ruido_gaussiano[[2]], colormode = 'Color'), method='r')
title('Imagen 5 sin ruido')

En segundo lugar, vamos a visualizar las imágenes originales y las imágenes sin ruido redimensionadas a su tamaño original, usando la función resize_imwd_to_original.

images_sin_ruido <- Map(resize_imwd_to_original, images_sin_ruido_gaussiano, c("4", "5") )
par(mfrow = c(2, 2), cex = 0.5)

display(Image(images[[4]], colormode = 'Color'), method='r')
title('Imagen 4 original')
display(Image(images_sin_ruido[[1]], colormode = 'Color'), method='r')
title('Imagen 4 sin ruido')

display(Image(images[[5]], colormode = 'Color'), method='r')
title('Imagen 5 original')
display(Image(images_sin_ruido[[2]], colormode = 'Color'), method='r')
title('Imagen 5 sin ruido')

Vemos que efectivamente, la imagen que originalemente contaba con un número de píxeles mucho menor, la imagen 5, presenta una gran distorsión tras la eliminación de ruido.

rm(images_for_imwd_cut_1,images_sin_ruido_gaussiano)

A continuación vamos a comprobar que es lo que ocurre cuando añadimos ruido sintético sinusoidal y si la frecuencia de este afecta al resultado de la eliminación de ruido. Trabajaremos con una única fotografía: la imagen 1.

# Añadimos ruido sinusoidal a las imágenes con sd = 0.3
image_1_sinosuidal_high <- add_noise_to_image("1", "sinusoidal_high", list(frequency = 50, amplitude = 0.3))
image_1_sinosuidal_low <- add_noise_to_image("2", "sinusoidal_low", list(frequency = 5, amplitude = 0.3))

# Hacemos una lista con las imágenes con ruido a redimensionar
images_for_imwd_cut_2 <- list(image_1_sinosuidal_high , image_1_sinosuidal_low )
names(images_for_imwd_cut_2) <- c('1 Noise: sinusoidal high', '1 Noise: sinusoidal low')

# Eliminamos variables innecesarias
rm(image_1_sinosuidal_high)
rm(image_1_sinosuidal_low)
images_recortadas <- lapply(images_for_imwd_cut_2, resize_imwd)
Warning: Assuming third dimension corresponds to colourWarning: Assuming third dimension corresponds to colour
images_sin_ruido_sinusoidal<- lapply(images_recortadas, procesar_imagen_wavelet)

Esta claro que los parámetros por defecto de la función threshold no son capaces de eliminar el ruido de tipo sinusoidal de la manera en que si lo era con el ruido de tipo Gaussiano, un tipo de ruido aleatorio, al contrario que el sinusoidal, que es una señal periódica.

par(mfrow = c(2, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia alta')
display(Image(images_sin_ruido_sinusoidal[[1]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia baja')
display(Image(images_sin_ruido_sinusoidal[[2]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

Vamos a variar parámetros de la función threshold para intentar quitar este ruido de manera más manual. En primer lugar, cambiamos el número de niveles al que aplicamos el umbral, para incluirlos a todos. Cambiamos policy a “manual” y variamos el valor del umbral de forma manual hasta encontrar uno que sea satisfactorio.

procesar_imagen_wavelet_sinusoidal <- function(foto, tipo = "hard", policy = "universal") {
  # 1. Realizamos la transformada wavelet a cada canal
  lwd <- lapply(1:3, function(canal) {
    imwd(foto[,,canal])  
  })
  
  # 2. Aplicamos el umbral a los coeficientes de la transformada wavelet
  lwd_threshold <- lapply(lwd, function(canal_wd) {
    niveles <- canal_wd$nlevels
    wavethresh::threshold(canal_wd, levels = 1:(niveles-1), type = tipo, policy = policy,by_level=TRUE,compression=FALSE, value = 4)
  })
  # 3. Aplicamos la transformada wavelet inversa a cada canal umbralizado
  ilwd <- lapply(lwd_threshold, function(canal_umbralizado) {
    wavethresh::imwr(canal_umbralizado)  # Transformada wavelet inversa
  })
  
  # 4. Reconstruir la imagen combinando los tres canales procesados
  imagen_reconstruida <- abind::abind(ilwd[[1]], ilwd[[2]], ilwd[[3]], along = 3)
    imagen <- Image(imagen_reconstruida, colormode = 'Color')
  
  return(imagen)
}
images_sin_ruido_sinusoidal<- lapply(images_recortadas, procesar_imagen_wavelet_sinusoidal, tipo ="soft", policy = "manual")

Con un valor de umbral de 4 y variando el tipo a “soft”, observamos que hemos conseguido eliminar el ruido sinusoidal de alta frecuencia, pero pagando un precio muy alto: los bordes de la imágen se disorsionan completamente y tenemos una muy baja resolución. Por otro lado, el ruido de frecuencia baja, aunque ha disminuido, claramente sigue presente en la imagen. El umbral necesario para eliminarlo con este método es tan alto que distorsionaría la imagen casi por completo.

par(mfrow = c(2, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia alta')
display(Image(images_sin_ruido_sinusoidal[[1]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 1 con ruido de frecuencia baja')
display(Image(images_sin_ruido_sinusoidal[[2]], colormode = 'Color'), method='r')
title('Imagen 1 sin ruido')

Probamos otros tipos de ruido: gamma, salt and pepper y ruido uniforme en las imágenes 2 y 3.

# Añadimos ruido a imágenes
image_2_salt_pepper <- add_noise_to_image("2", "salt_pepper", list(epsilon = 0.1))
image_3_gamma <- add_noise_to_image("3", "gamma", list(looks = 2))
image_3_uniform <- add_noise_to_image("3", "uniform_multiplicative", list(looks = 2))


# Hacemos una lista con las imágenes con ruido a recortar
images_for_imwd_cut_3 <- list( image_2_salt_pepper, image_3_gamma, image_3_uniform)
names(images_for_imwd_cut_3) <- c('2 Noise: salt and pepper', '3 Noise: gamma','3 Noise: uniform')

# Eliminamos variables innecesarias
rm(image_2_salt_pepper, image_3_gamma, image_3_uniform)
=======

4.2 Función imwd

Función para hacer cuadrada la imagen.

Para aplicar el algoritmo de Deformación Iterativa de Mallat (IMWD), es necesario que la imagen tenga una forma cuadrada. Dado que muchas imágenes no son cuadradas, es necesario convertirlas antes de aplicar el algoritmo.

A continuación, se presenta una función de pre-procesamiento, que ajusta cualquier imagen rectangular a un tamaño cuadrado, manteniendo sus proporciones originales al agregar relleno (fondo blanco) si es necesario. Esta transformación asegura que la imagen sea compatible con el algoritmo IMWD.

hacer_cuadrada_potencia_2 <- function(imagen) {
  n_filas <- dim(imagen)[1]
  n_columnas <- dim(imagen)[2]
  
  nuevo_tamano <- max(n_filas, n_columnas)
  
  siguiente_potencia_2 <- 2^ceiling(log2(nuevo_tamano))
  
  imagen_cuadrada <- array(0, dim = c(siguiente_potencia_2, siguiente_potencia_2, dim(imagen)[3])) 
  
  imagen_cuadrada[1:n_filas, 1:n_columnas, ] <- imagen
  
  return(imagen_cuadrada)
}
>>>>>>> Alejandra
<<<<<<< HEAD

Suguiendo el mismo procedimiento, redimensionamos las imágenes y y realizamos las transformadas wavelet y el thresholding con la función procesar_imagen_wavelet.

images_recortadas <- lapply(images_for_imwd_cut_3, resize_imwd)
Warning: Assuming third dimension corresponds to colourWarning: Assuming third dimension corresponds to colourWarning: Assuming third dimension corresponds to colour
images_sin_ruidos<- lapply(images_recortadas, procesar_imagen_wavelet)
=======

Cargar imagenes en la función cuadrada

Aplicamos la función en 3 fotos

fotos_cuadradas <- lapply(imagen_noise, hacer_cuadrada_potencia_2)
>>>>>>> Alejandra <<<<<<< HEAD

Visualizamos los resultados. Parece que el algoritmo funciona correctamente para los tres tipos de ruido.

par(mfrow = c(3, 2), cex = 0.5)

display(Image(images_recortadas[[1]], colormode = 'Color'), method='r')
title('Imagen 2 con salt and pepper')
display(Image(images_sin_ruidos[[1]], colormode = 'Color'), method='r')
title('Imagen 2 sin ruido')

display(Image(images_recortadas[[2]], colormode = 'Color'), method='r')
title('Imagen 3 con ruido gamma')
display(Image(images_sin_ruidos[[2]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

display(Image(images_recortadas[[3]], colormode = 'Color'), method='r')
title('Imagen 3 con ruido uniforme')
display(Image(images_sin_ruidos[[3]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

=======

Imagen Original

>>>>>>> Alejandra <<<<<<< HEAD

Redimensionamos las imágenes obtenidas a su tamaño original para poder compararlas con estas. Vemos que en este caso la eliminación de ruido ha sido muy buena y no hay apenas distorsión ni suavizado de los bordes.

images_sin_ruido <- Map(resize_imwd_to_original, images_sin_ruidos, c("2", "3", "3") )
par(mfrow = c(3, 2), cex = 0.5)

display(Image(images[[2]], colormode = 'Color'), method='r')
title('Imagen 2 original')
display(Image(images_sin_ruido[[1]], colormode = 'Color'), method='r')
title('Imagen 2 sin ruido')

display(Image(images[[3]], colormode = 'Color'), method='r')
title('Imagen 3 original')
display(Image(images_sin_ruido[[2]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

display(Image(images[[3]], colormode = 'Color'), method='r')
title('Imagen 3 original')
display(Image(images_sin_ruido[[3]], colormode = 'Color'), method='r')
title('Imagen 3 sin ruido')

rm(images_recortadas, images_sin_ruido, images_sin_ruidos)
=======
[1] "1 Noise: gaussian"
[1] "2 Noise: gamma"

[1] "3 Noise: unif"

Cuando uso fotos de mayor calidad solo acepta hasta 2, con 3 aparece esto: Error: vector memory limit of 16.0 Gb reached, see mem.maxVSize()

El umbral “universal”, propuesta por Donoho y Johnstone, es un método utilizado en la eliminación de ruido en señales e imágenes mediante transformadas de wavelet. Esta estrategia calcula el umbral aplicado a los coeficientes de wavelet en función del tamaño de la señal y una estimación del nivel de ruido. La fórmula del umbral “universal” es \[ \sigma \sqrt{2 \log n}\] donde \(\sigma\) es una estimación del ruido y n es el número de muestras o elementos de la señal.

Imagenes sin ruido

Error in slot(prueba, ".Data")[1:1600, 1:1066, , drop = FALSE] : 
  subscript out of bounds

Ruido gaussiano

Imagen con ruido Gaussiano original

  1. Imagen Cortada sin filtro

  2. Ajusta el contraste de la imagen, incrementando la diferencia entre los píxeles claros y oscuros.

  3. Imagen con filtro paso alto

4.3 Función denoise…

>>>>>>> Alejandra

8 Conclusiones

<<<<<<< HEAD
LS0tCnRpdGxlOiAiRWxpbWluYWNpw7NuIGRlIHJ1aWRvIGVuIGltw6FnZW5lcyBjb24gd2F2ZWxldHMuIgpzdWJ0aXRsZTogIkFuw6FsaXNpcyBkZSBzZcOxYWxlcyIKYXV0aG9yOiAiR3J1cG8gRTogQWxlamFuZHJhIFZlbmVnYXMsIFJlYmVjYSBDb21wYW55LCBNYXJ0YSBNZWRpbmEsIEFsZWphbmRybyBDb3JuZWxpbyB5IElsaWEgWmhpZ2FyZXYuIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBib29rZG93bjo6cGRmX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGZpZ3VyZV9jYXB0aW9uOiAiRmlndXJhIiAjIFJlZmVyZW5jaWFzIGVuIGNhc3RlbGxhbm8KICAgIHRhYmxlX2NhcHRpb246ICJUYWJsYSIKICBodG1sX2RvY3VtZW50OgogICAgZWNobzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0aGVtZTogbHVtZW4KICAgIHRvYzogdHJ1ZQogIGh0bWxfbm90ZWJvb2s6CiAgICBlY2hvOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICBlY2hvOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB0cnVlCiAgICBmaWd1cmVfY2FwdGlvbjogIkZpZ3VyYSIKICAgIHRhYmxlX2NhcHRpb246ICJUYWJsYSIKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKcGFyYW1zOgogIGxhbmc6IEVTCmxhbmc6ICJgciBzd2l0Y2gocGFyYW1zJGxhbmcsIEVTID0gJ2VzLUVTJywgRU4gPSAnZW4tVVMnKWAiCmxhbmd1YWdlOgogIGxhYmVsOgogICAgZmlnOiAnRmlndXJhICcKICAgIHRhYjogJ1RhYmxhICcKICAgIGVxOiAnRWN1YWNpw7NuICcKICAgIHRobTogJ1Rlb3JlbWEgJwogICAgbGVtOiAnTGVtYSAnCiAgICBkZWY6ICdEZWZpbmljacOzbiAnCiAgICBjb3I6ICdDb3JvbGFyaW8gJwogICAgcHJwOiAnUHJvcG9zaWNpw7NuICcKICAgIGV4bTogJ0VqZW1wbG8gJwogICAgZXhyOiAnRWplcmNpY2lvICcKICAgIHByb29mOiAnRGVtb3N0cmFjacOzbi4gJwogICAgcmVtYXJrOiAnTm90YTogJwogICAgc29sdXRpb246ICdTb2x1Y2nDs24uICcKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpybShsaXN0ID0gbHMoKSkKYGBgCgojIEludHJvZHVjY2nDs24KCkVuIGVsIGNhbXBvIGRlbCBhbsOhbGlzaXMgZGUgc2XDsWFsZXMsIHVubyBkZSBsb3MgcHJvYmxlbWFzIG3DoXMgcmVsZXZhbnRlcyBlcyBsYSBlbGltaW5hY2nDs24gZGUgcnVpZG8gZW4gaW3DoWdlbmVzIGRpZ2l0YWxlcy4gTGFzIHNlw7FhbGVzLCBxdWUgcmVwcmVzZW50YW4gdmFyaWFjaW9uZXMgZGUgbWFnbml0dWRlcyBlbiBlbCBlc3BhY2lvIHkvbyB0aWVtcG8sIGEgbWVudWRvIGVzdMOhbiBjb250YW1pbmFkYXMgcG9yIHJ1aWRvIGRlYmlkbyBhIGludGVyZmVyZW5jaWFzIGVuIGxvcyBwcm9jZXNvcyBkZSBjYXB0dXJhIG8gdHJhbnNtaXNpw7NuLiBFc3RlIHJ1aWRvLCBxdWUgcHVlZGUgZXhpc3RpciBkZSBmb3JtYXMgbXV5IGRpdmVyc2FzIGNvbW8gcnVpZG8gZ2F1c3NpYW5vLCBhcnRlZmFjdG9zIGFzb2NpYWRvcyBhIGZyZWN1ZW5jaWFzIGFsdGFzIG8gYmFqYXMsIG8gZWZlY3RvcyBtdWx0aXBsaWNhdGl2b3MsIGNvbXBsaWNhIGxhIGV4dHJhY2Npw7NuIGRlIGNhcmFjdGVyw61zdGljYXMgc2lnbmlmaWNhdGl2YXMuCgpFbCB1c28gZGUgd2F2ZWxldHMgZXMgdW5hIGhlcnJhbWllbnRhIGNsYXZlIHBhcmEgYWJvcmRhciBlc3RlIHByb2JsZW1hLCBlbCBjdWFsIHBlcm1pdGUgZGVzY29tcG9uZXIgc2XDsWFsZXMgZW4gc3VzIGNvbXBvbmVudGVzIGVzcGFjaWFsZXMgeSBmcmVjdWVuY2lhbGVzIGRlIGZvcm1hIGVmaWNpZW50ZS4gRXN0ZSBtw6l0b2RvIG9mcmVjZSBsYSBwb3NpYmlsaWRhZCBkZSBhZGFwdGFyc2UgYSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBwYXJ0aWN1bGFyZXMgZGUgY2FkYSB0aXBvIGRlIHJ1aWRvLgoKRW4gZXN0ZSB0cmFiYWpvIHNlIHByb3BvbmUgZXhwbG9yYXIgbGEgY2FwYWNpZGFkIGRlIGxvcyB3YXZlbGV0cyBwYXJhIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbyBlbiBpbcOhZ2VuZXMgZGlnaXRhbGVzLiBQYXJhIGVsbG8sIHNlIGdlbmVyYXLDoW4geSBhbmFsaXphcsOhbiBkaWZlcmVudGVzIHRpcG9zIGRlIHJ1aWRvIGFydGlmaWNpYWwsIGV2YWx1YW5kbyBzdSBjb21wbGVqaWRhZCBwYXJhIHN1IGF0ZW51YWNpw7NuIHkgZGV0ZXJtaW5hbmRvIGxvcyBwYXLDoW1ldHJvcyBtw6FzIGVmaWNhY2VzIGRlIGxvcyB3YXZlbGV0cy4KCiMgRnVuZGFtZW50byB0ZcOzcmljbzogZWxpbWluYWNpw7NuIGRlIHJ1aWRvIGNvbiB3YXZlbGV0cwoKTG9zIHdhdmVsZXRzIHNvbiBmdW5jaW9uZXMgbWF0ZW3DoXRpY2FzIHF1ZSBwZXJtaXRlbiBkZXNjb21wb25lciB1bmEgc2XDsWFsIGVuIGNvbXBvbmVudGVzIGRlIGRpZmVyZW50ZXMgZXNjYWxhcywgbG8gcXVlIHJlc3VsdGEgw7p0aWwgcGFyYSBpZGVudGlmaWNhciB5IHByb2Nlc2FyIGNhcmFjdGVyw61zdGljYXMgZXNwZWPDrWZpY2FzLiBFc3RlIGVuZm9xdWUgZXMgZXNwZWNpYWxtZW50ZSByZWxldmFudGUgZW4gbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvLCBkb25kZSBsYXMgZnJlY3VlbmNpYXMgaW5kZXNlYWRhcyBwdWVkZW4gc2VyIHNlcGFyYWRhcyB5IHJlZHVjaWRhcyBzaW4gYWZlY3RhciBzaWduaWZpY2F0aXZhbWVudGUgbGFzIGNhcmFjdGVyw61zdGljYXMgcHJpbmNpcGFsZXMgZGUgbGEgc2XDsWFsIG9yaWdpbmFsLiBBIGRpZmVyZW5jaWEgZGUgbGEgVHJhbnNmb3JtYWRhIGRlIEZvdXJpZXIsIHF1ZSBvcGVyYSBnbG9iYWxtZW50ZSB5IG5vIG9mcmVjZSBpbmZvcm1hY2nDs24gc29icmUgbGEgbG9jYWxpemFjacOzbiB0ZW1wb3JhbCBkZSBsb3MgZXZlbnRvcywgbG9zIHdhdmVsZXRzIHBlcm1pdGVuIHVuIGFuw6FsaXNpcyBsb2NhbGl6YWRvLCBmYWNpbGl0YW5kbyBsYSBpZGVudGlmaWNhY2nDs24gZGUgcGF0cm9uZXMgeSBhbm9tYWzDrWFzIGVuIGxvcyBkYXRvcy4KCkVsIHByb2Nlc28gZGUgcmVkdWNjacOzbiBkZSBydWlkbyBjb24gd2F2ZWxldHMgZ2VuZXJhbG1lbnRlIGluY2x1eWUgdHJlcyBldGFwYXMgcHJpbmNpcGFsZXM6IGxhIGRlc2NvbXBvc2ljacOzbiBkZSBsYSBzZcOxYWwgdXRpbGl6YW5kbyB1bmEgd2F2ZWxldCBtYWRyZSwgbGEgbW9kaWZpY2FjacOzbiBkZSBsb3MgY29lZmljaWVudGVzIHdhdmVsZXQgbWVkaWFudGUgdMOpY25pY2FzIGRlIHVtYnJhbCwgeSBsYSByZWNvbnN0cnVjY2nDs24gZGUgbGEgc2XDsWFsLiBMYSBzZWxlY2Npw7NuIGRlIGxhIHdhdmVsZXQgbWFkcmUgYWRlY3VhZGEgeSBsb3MgcGFyw6FtZXRyb3MgZGUgdW1icmFsIHNvbiBhc3BlY3RvcyBjcsOtdGljb3MgcXVlIGRlYmVuIGFkYXB0YXJzZSBhIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGVzcGVjw61maWNhcyBkZWwgcnVpZG8geSBsYSBzZcOxYWwuCgpBZGVtw6FzLCBsb3Mgd2F2ZWxldHMgb2ZyZWNlbiB1biBlbmZvcXVlIG11bHRpLXJlc29sdWNpw7NuLCBwZXJtaXRpZW5kbyB1bmEgcmVwcmVzZW50YWNpw7NuIGRldGFsbGFkYSBkZSBsb3MgY29tcG9uZW50ZXMgZGUgYWx0YSBmcmVjdWVuY2lhLCBhc29jaWFkb3MgZnJlY3VlbnRlbWVudGUgY29uIGVsIHJ1aWRvLCBtaWVudHJhcyBwcmVzZXJ2YW4gbGFzIGVzdHJ1Y3R1cmFzIGdsb2JhbGVzIGRlIGJhamEgZnJlY3VlbmNpYS4gRXN0YSBjYXJhY3RlcsOtc3RpY2EgaGFjZSBxdWUgbG9zIHdhdmVsZXRzIHNlYW4gZXNwZWNpYWxtZW50ZSDDunRpbGVzIGVuIGFwbGljYWNpb25lcyBkb25kZSBsYSBwcmVjaXNpw7NuIHkgbGEgaW50ZWdyaWRhZCBkZSBsb3MgZGF0b3Mgc29uIGVzZW5jaWFsZXMsIGNvbW8gZW4gaW3DoWdlbmVzIG3DqWRpY2FzLCBwcm9jZXNhbWllbnRvIGRlIGF1ZGlvIG8gYW7DoWxpc2lzIGRlIGRhdG9zIGNpZW50w61maWNvcy4KCkVuIGVzdGUgdHJhYmFqbywgc2UgZW1wbGVhcsOhbiB3YXZlbGV0cyBjb21vIGhlcnJhbWllbnRhIHByaW5jaXBhbCBwYXJhIGVsaW1pbmFyIGRpZmVyZW50ZXMgdGlwb3MgZGUgcnVpZG8gc2ludMOpdGljbyBlbiBpbcOhZ2VuZXMuCgojIEZ1bmNpb25lcyBkZSBwcm9ncmFtYWNpb24gZW1wbGVhZGFzCgojIERlc2Fycm9sbG8geSByZXN1bHRhZG9zCgojIEZ1bmRhbWVudG8gdGXDs3JpY286IGVsaW1pbmFjacOzbiBkZSBydWlkbyBjb24gd2F2ZWxldHMKCiMgRnVuY2lvbmVzIGRlIHByb2dyYW1hY2lvbiBlbXBsZWFkYXMKCgoqKkVsaW1pbmFjacOzbiBkZSBydWlkbyBjb24gZWwgYWxnb3JpdG1vIGRlIE1hbGxhdCoqCgpFbXBsZWFtb3MgbGEgZnVuaWPDs24gYGltd2QoKWAgZGVsIHBhcXVldGUgd2F2ZXRocmVzaC4gRXN0w6EgZnVuY2nDs24gcmVhbGl6YSB1bmEKdHJhbnNmb3JtYWRhIGRpc2NyZXRhIHdhdmVsZXQgZGUgYWN1ZXJkbyBjb24gZWwgYWxnb3JpdG1vIGRlIE1hbGxhdC4KCi0gYGltd2QoaW1hZ2UsIGZpbHRlci5udW1iZXI9MTAsIGZhbWlseT0iRGF1YkxlQXN5bW0iLCB0eXBlPSJ3YXZlbGV0IiwuLi4pLmAKCkVsIGFyZ3VtZW50byBgaW1hZ2VgIGRlIGxhIGZ1bmNpw7NuIGRlYmUgc2VyIHVuYSBtYXRyaXogY3VhZHJhZGEgY3V5YSBkaW1lbnNpw7NuIApzZWEgcG90ZW5jaWEgZGUgZG9zLmBmaWx0ZXIubnVtYmVyYCBlbGlnZSBsYSBzdWF2aWRhZCBkZSBsYSB3YXZlbGV0IGEgZW1wbGVhciwgc2llbmRvIHBvciBkZWZlY3RvIDEwLiBgZmFtaWx5YCBpbmRpY2EgbGEgZmFtaWxpYSBkZSB3YXZlbGV0cyBhIGVtcGxlYXIgKCJEYXViRXhQaGFzZSIgw7MgIkRhdWJMZUFzeW1tIikuUGFyYSB0cmF0YXIgbGFzIGZyb250ZXJhcywgbWFudGVuZHJlbW9zIGVsIHBhcsOhbWV0cm8gYGJjID0gInBlcmlvZGljImAgcG9yIGRlZmVjdG8uCgpQYXJhIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbyBhcGxpY2FyZW1vcyBsYSBmdW5jacOzbiBgdGhyZXNob2xkKClgIGFsIG9iamV0byBxdWUKZGV2dWVsdmUgbGEgZnVuY2nDs24gYGltd2QoKWAuIAoKLSBgdGhyZXNob2xkKGltd2QsIGxldmVscyA9IDM6KG5sZXZlbHNXVChpbXdkKSAtIDEpLCB0eXBlID0gImhhcmQiLCBwb2xpY3kgPSAidW5pdmVyc2FsIiwgYnkubGV2ZWwgPSBGQUxTRSwgdmFsdWUgPSAwLCByZXR1cm4udGhyZXNob2xkID0gRkFMU0UsIGNvbXByZXNzaW9uID0gVFJVRSwgUSA9IDAuMDUsIC4uLilgCgpgbGV2ZWxzYCBlcyBlbCBuw7ptZXJvIGRlIG5pdmVsZXMgYSBsb3MgY3XDoWxlcyBkZXNlYW1vcyBhcGxpY2FyIHVuIHVtYnJhbCwgbWllbnRyYXMgcXVlIGB0eXBlYCBpbmRpY2Egc2kgcXVlcmVtb3MgdW4gdW1icmFsIG3DoXMgc3VhdmUgKCJzb2Z0Iikgw7MgbcOhcyBmdWVydGUgKCJoYXJkIikuIEVsIHBhcsOhbWV0cm8gYHBvbGljeWAgc2VsZWNjaW9uYSBsYSB0w6ljbmljYSBwYXJhIGVsZWdpciB1bWJyYWwuIGBieS5sZXZlbCA9IEZBTFNFYCBzaWduaWZpY2EgcXVlIHNlIGFwbGljYSB1biB1bWJyYWwgZ2xvYmFsIGEgdG9kb3MgbG9zIG5pdmVsZXMgaW5kaWNhZG9zLCBtaWVudHJhcyBxdWUgYGJ5LmxldmVsID0gVFJVRWAgY2FsY3VsYXIgdGhyZXNob2xkIHBhcmEgY2FkYSBuaXZlbCBwb3Igc2VhcGFyYWRvLiBFbCBwYXLDoW1ldHJvIGB2YWx1ZWAsIGVsIHZhbG9yIGRlbCB1bWJyYWwsIHNlIHVzYXLDoSBzaSBlbGVnaW1vcyB0w6ljbmljYXMgbWFudWFsZXMgZW4gYHBvbGljeWAuIFNpIHF1ZXJlbW9zIG9idGVuZXIgZWwgdmFsb3IgdW1icmFsIGFwbGljYWRvLCBwb25kcmVtb3MgYHJldHVybi50aHJlc2hvbGQgPSBUUlVFYC4gRmluYWxtZW50ZSwgZGVqYXJlbW9zIGVsIHBhcsOhbWV0cm8gYGNvbXByZXNzaW9uID0gVFJVRWAgcG9yIGRlZmVjdG8sIHBhcmEgb2J0ZXJuZXIgdW4gb2JqZXRvIG3DoXMgcGVxdWXDsW8uIAoKRHVyYW50ZSBlbCBkZXNhcnJvbGxvIGRlbCB0cmFiYWpvIHZhcmlhcmVtb3MgZXN0b3MgcGFyw6FtZXRyb3MgcGFyYSBvYnNlcnZhciBzdSBlZmVjdG8gZW4gbGEgZWxpbWluYWNpw7NuIGRlIGRpdmVyc29zIHRpcG9zIGRlIHJ1aWRvIHkgZW5jb250YXIgYXF1ZWxsb3MgcXVlIGdlbmVyZW4gdW4gbWVqb3IgcmVzdWx0YWRvIGVuIGNhZGEgY2Fzby4gVW5hIHZleiByZWFsaXphZG8gZWwgdGhyZXNob2xkaW5nLCBlbXBsZWFyZW1vcyBsYSBmdW5jacOzbiBgaW13cmAgcGFyYSByZWFsaXphciBsYSB0cmFuc2Zvcm1kYSB3YXZlbGV0IGludmVyc2EgeSBwb2RlciB2aXN1YWxpemFyIGxhIGltYWdlbiB0cmFzIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkby4KCiMgRGVzYXJyb2xsbyB5IHJlc3VsdGFkb3MKCgoKQW50ZXMgZGUgY29tZW56YXIsIGluc3RhbGFtb3MgeS9vIGNhcmdhbW9zIHRvZG9zIGxvcyBwYXF1ZXRlcyByZXF1ZXJpZG9zLgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQpsaWJyYXJ5KHBhY21hbikKcF9sb2FkKGltYWdlciwgd2F2ZXRocmVzaCwgZ2dwbG90MiwgZHBseXIsIFNwYXRpYWxQYWNrLCB3YXZlc2xpbSwgRUJJbWFnZSwgc3RyaW5nciwganBlZywgYWJpbmQsIG1hZ2ljaykKYGBgCgpDb21lbnphbW9zIGNhcmdhbmRvIHkgdmlzdWFsaXphbmRvIGxhcyBmb3RvZ3JhZsOtYXMgYSBlbXBsZWFyLgpgYGB7cn0KaW1hZ2VzX3BhdGggPC0gbGlzdC5maWxlcygiLi9mb3RvcyIsIGZ1bGwubmFtZXMgPSBUUlVFKQoKbm9tYnJlc19pbWFnZXMgPC0gc3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gc3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gaW1hZ2VzX3BhdGgsIHBhdHRlcm4gPSAiLi9mb3Rvcy8iKSwgcGF0dGVybiA9ICJcXC5KUEd8XFwuanBnIikKCmltYWdlcyA8LSBsYXBwbHkoaW1hZ2VzX3BhdGgsIHJlYWRKUEVHKSAjIENhcmdhbW9zIGxhcyBpbcOhZ2VuZXMKbmFtZXMoaW1hZ2VzKSA8LSBub21icmVzX2ltYWdlcwpgYGAKCmBgYHtyfQojIFJvdGFtb3MgYWxndW5hcyBkZSBsYXMgZm90b3MgcGFyYSB1bmEgdmlzdWFsaXphY2nDs24gbcOhcyB1bmlmb3JtZQpmb3Rvc19hX2dpcmFyIDwtIGMoIjEiLCAiMiIsICIzIiwgIjQiKQppbWFnZXNfcm90YWRhcyA8LSBsYXBwbHkoaW1hZ2VzW2ZvdG9zX2FfZ2lyYXJdLCBhcGVybSwgcGVybSA9IGMoMiwgMSwgMykpCgoKZm9yIChpIGluIGZvdG9zX2FfZ2lyYXIpIHsKICBpbWFnZXNfcm90YWRhc1tbaV1dIDwgaW1hZ2VzX3JvdGFkYXNbW2ldXVtkaW0oaW1hZ2VzX3JvdGFkYXNbW2ldXSlbMV06MSwgLCBdCn0KCmltYWdlc1tmb3Rvc19hX2dpcmFyXSA8LSBpbWFnZXNfcm90YWRhcwpybShpbWFnZXNfcm90YWRhcykKcm0oZm90b3NfYV9naXJhcikKYGBgCgoKYGBge3J9CiMgVmlzdWFsaXphbW9zIGxhcyBpbWFnZW5lcyBvcmlnaW5hbGVzIChjb21lbnRvIHBhcmEgcXVlIG5vIHRhcmRlIGVuIGVqZWN1dGFyKQoKI3BhcihtZnJvdyA9IGMoMiwgMyksIG1hciA9IGMoMSwgMSwgMSwgMSkpCgojIGZvciAoaW1nIGluIG5vbWJyZXNfaW1hZ2VzKSB7CiMgICBkaXNwbGF5KEltYWdlKGltYWdlc1tbaW1nXV0sIGNvbG9ybW9kZSA9ICJDb2xvciIpLCBtZXRob2QgPSAiciIpCiMgICB0aXRsZShwYXN0ZSgnSW1hZ2VuJywgaW1nKSkKIyB9CmBgYAoKIyMgSW5jbHVzacOzbiBkZSBydWlkbyBzaW50w6l0aWNvIGVuIGxhcyBpbcOhZ2VuZXMKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNH0KIyBEZWZpbmljacOzbiBkZSB0aXBvcyBkZSBydWlkbwoKTk9JU0VfVFlQRVMgPC0gbGlzdCgKICBnYXVzc2lhbiA9IGxpc3QoCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsKICAgICAgIyBEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGVsIHJ1aWRvIGNvbiB1biB2YWxvciBwcmVkZXRlcm1pbmFkbwogICAgICBub2lzZV9zdGRfZGV2IDwtIHBhcmFtcyRzdGRfZGV2ICV8fCUgMC41CgogICAgICAjIEdlbmVyYWNpw7NuIGRlIHJ1aWRvIGdhdXNzaWFubwogICAgICBub2lzZSA8LSBhcnJheSgKICAgICAgICBybm9ybShsZW5ndGgoY2hhbm5lbCksIG1lYW4gPSAwLCBzZCA9IG5vaXNlX3N0ZF9kZXYpLAogICAgICAgIGRpbSA9IGRpbShjaGFubmVsKQogICAgICApCgogICAgICAjIEFzZWd1cmFyIHF1ZSBsb3MgdmFsb3JlcyBlc3TDqW4gZW50cmUgMCB5IDEKICAgICAgcG1heCgwLCBwbWluKDEsIGNoYW5uZWwgKyBub2lzZSkpCiAgICB9CiAgKSwKICBzaW51c29pZGFsX2hpZ2ggPSBsaXN0KAogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7CiAgICAgICMgRnJlY3VlbmNpYSB5IGFtcGxpdHVkIGRlbCBydWlkbyBzaW51c29pZGFsIGRlIGFsdGEgZnJlY3VlbmNpYQogICAgICBmcmVxdWVuY3kgPC0gcGFyYW1zJGZyZXF1ZW5jeSAlfHwlIDI1CiAgICAgIGFtcGxpdHVkZSA8LSBwYXJhbXMkYW1wbGl0dWRlICV8fCUgMC4yCgogICAgICAjIEdlbmVyYWNpw7NuIGRlIHJ1aWRvIHNpbnVzb2lkYWwKICAgICAgaGVpZ2h0IDwtIGRpbShjaGFubmVsKVsxXQogICAgICB3aWR0aCA8LSBkaW0oY2hhbm5lbClbMl0KICAgICAgeCA8LSBzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gd2lkdGgpCiAgICAgIHkgPC0gc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IGhlaWdodCkKICAgICAgbm9pc2VfZ3JpZCA8LSBvdXRlcihzaW4oeCAqIGZyZXF1ZW5jeSksIHNpbih5ICogZnJlcXVlbmN5KSkKCiAgICAgICMgQXBsaWNhciBlbCBydWlkbwogICAgICBub2lzZSA8LSBhcnJheShub2lzZV9ncmlkICogYW1wbGl0dWRlLCBkaW0gPSBkaW0oY2hhbm5lbCkpCiAgICAgIHBtYXgoMCwgcG1pbigxLCBjaGFubmVsICsgbm9pc2UpKQogICAgfQogICksCiAgc2ludXNvaWRhbF9sb3cgPSBsaXN0KAogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7CiAgICAgICMgRnJlY3VlbmNpYSB5IGFtcGxpdHVkIGRlbCBydWlkbyBzaW51c29pZGFsIGRlIGJhamEgZnJlY3VlbmNpYQogICAgICBmcmVxdWVuY3kgPC0gcGFyYW1zJGZyZXF1ZW5jeSAlfHwlIDIKICAgICAgYW1wbGl0dWRlIDwtIHBhcmFtcyRhbXBsaXR1ZGUgJXx8JSAwLjIKCiAgICAgICMgR2VuZXJhY2nDs24gZGUgcnVpZG8gc2ludXNvaWRhbAogICAgICBoZWlnaHQgPC0gZGltKGNoYW5uZWwpWzFdCiAgICAgIHdpZHRoIDwtIGRpbShjaGFubmVsKVsyXQogICAgICB4IDwtIHNlcSgwLCAyICogcGksIGxlbmd0aC5vdXQgPSB3aWR0aCkKICAgICAgeSA8LSBzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gaGVpZ2h0KQogICAgICBub2lzZV9ncmlkIDwtIG91dGVyKHNpbih4ICogZnJlcXVlbmN5KSwgc2luKHkgKiBmcmVxdWVuY3kpKQoKICAgICAgIyBBcGxpY2FyIGVsIHJ1aWRvCiAgICAgIG5vaXNlIDwtIGFycmF5KG5vaXNlX2dyaWQgKiBhbXBsaXR1ZGUsIGRpbSA9IGRpbShjaGFubmVsKSkKICAgICAgcG1heCgwLCBwbWluKDEsIGNoYW5uZWwgKyBub2lzZSkpCiAgICB9CiAgKSwKICBzYWx0X3BlcHBlciA9IGxpc3QoCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsKICAgICAgIyBQcm9wb3JjacOzbiBkZSBww614ZWxlcyBhZmVjdGFkb3MgcG9yIGVsIHJ1aWRvIGRlIHNhbCB5IHBpbWllbnRhCiAgICAgIGVwc2lsb24gPC0gcGFyYW1zJGVwc2lsb24gJXx8JSAwLjIKCiAgICAgICMgR2VuZXJhY2nDs24gZGUgcnVpZG8KICAgICAgbm9pc2UgPC0gbWF0cml4KHNhbXBsZShjKDAsIDEsIE5BKSwgbGVuZ3RoKGNoYW5uZWwpLCByZXBsYWNlID0gVFJVRSwgcHJvYiA9IGMoZXBzaWxvbiAvIDIsIGVwc2lsb24gLyAyLCAxIC0gZXBzaWxvbikpLAogICAgICAgIG5yb3cgPSBkaW0oY2hhbm5lbClbMV0sIG5jb2wgPSBkaW0oY2hhbm5lbClbMl0KICAgICAgKQogICAgICBjaGFubmVsWyFpcy5uYShub2lzZSldIDwtIG5vaXNlWyFpcy5uYShub2lzZSldCiAgICAgIGNoYW5uZWwKICAgIH0KICApLAogIGdhbW1hID0gbGlzdCgKICAgIGdlbmVyYXRvciA9IGZ1bmN0aW9uKGNoYW5uZWwsIHBhcmFtcykgewogICAgICAjIFJ1aWRvIG11bHRpcGxpY2F0aXZvIGdhbW1hIGNvbiBwYXLDoW1ldHJvIGRlIGRpc3BlcnNpw7NuCiAgICAgIGxvb2tzIDwtIHBhcmFtcyRsb29rcyAlfHwlIDIKICAgICAgbm9pc2UgPC0gYXJyYXkocmdhbW1hKGxlbmd0aChjaGFubmVsKSwgc2hhcGUgPSBsb29rcywgc2NhbGUgPSAxIC8gbG9va3MpLCBkaW0gPSBkaW0oY2hhbm5lbCkpCiAgICAgIHBtYXgoMCwgcG1pbigxLCBjaGFubmVsICogbm9pc2UpKQogICAgfQogICksCiAgdW5pZm9ybV9tdWx0aXBsaWNhdGl2ZSA9IGxpc3QoCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsKICAgICAgIyBSdWlkbyBtdWx0aXBsaWNhdGl2byB1bmlmb3JtZQogICAgICBsb29rcyA8LSBwYXJhbXMkbG9va3MgJXx8JSAyCiAgICAgIG5vaXNlX2NoYW5uZWwgPC0gU3BhdGlhbFBhY2s6Omltbm9pc2UoCiAgICAgICAgaW1nID0gY2hhbm5lbCwKICAgICAgICB0eXBlID0gInNwZWNrbGUiLAogICAgICAgIGxvb2tzID0gbG9va3MKICAgICAgKQogICAgICBwbWF4KDAsIHBtaW4oMSwgbm9pc2VfY2hhbm5lbCkpCiAgICB9CiAgKQopCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNH0KIyBGdW5jacOzbiBwYXJhIGHDsWFkaXIgcnVpZG8gYSB1bmEgaW1hZ2VuCmFkZF9ub2lzZV90b19pbWFnZSA8LSBmdW5jdGlvbihpbWFnZV9uYW1lLCBub2lzZV90eXBlLCBub2lzZV9wYXJhbXMgPSBsaXN0KCksIHBsb3QgPSBGQUxTRSkgewogICMgVmVyaWZpY2FyIHNpIGxhIGltYWdlbiBleGlzdGUgZW4gbGEgbGlzdGEKICBpZiAoIWltYWdlX25hbWUgJWluJSBuYW1lcyhpbWFnZXMpKSB7CiAgICBzdG9wKCJMYSBpbWFnZW4gY29uIGVzdGUgbm9tYnJlIG5vIHNlIGVuY3VlbnRyYSBlbiBsYSBsaXN0YSAnaW1hZ2VzJyIpCiAgfQoKICAjIFZlcmlmaWNhciBlbCB0aXBvIGRlIHJ1aWRvCiAgaWYgKCFub2lzZV90eXBlICVpbiUgbmFtZXMoTk9JU0VfVFlQRVMpKSB7CiAgICBzdG9wKAogICAgICAiRWwgdGlwbyBkZSBydWlkbyBlcyBkZXNjb25vY2lkby4gVGlwb3MgZGlzcG9uaWJsZXM6ICIsCiAgICAgIHBhc3RlKG5hbWVzKE5PSVNFX1RZUEVTKSwgY29sbGFwc2UgPSAiLCAiKQogICAgKQogIH0KCiAgIyBPYnRlbmVyIGxhIGltYWdlbiBvcmlnaW5hbCBkZSBsYSBsaXN0YQogIG9yaWdpbmFsX2ltYWdlIDwtIGltYWdlc1tbaW1hZ2VfbmFtZV1dCgogICMgQ29udmVydGlyIGxhIGltYWdlbiBhIHVuIGFycmF5IHNpIGVzIG5lY2VzYXJpbwogIGltYWdlX2FycmF5IDwtIGFzLmFycmF5KG9yaWdpbmFsX2ltYWdlKQoKICAjIEFwbGljYXIgcnVpZG8gYSBjYWRhIGNhbmFsCiAgbm9pc3lfY2hhbm5lbHMgPC0gbGFwcGx5KDE6MywgZnVuY3Rpb24oaSkgewogICAgY2hhbm5lbCA8LSBpbWFnZV9hcnJheVssICwgaV0KICAgIE5PSVNFX1RZUEVTW1tub2lzZV90eXBlXV0kZ2VuZXJhdG9yKGNoYW5uZWwsIG5vaXNlX3BhcmFtcykKICB9KQoKICAjIENyZWFyIGxhIGltYWdlbiBjb24gcnVpZG8KICBub2lzeV9pbWFnZV9hcnJheSA8LSBhcnJheSgKICAgIHVubGlzdChub2lzeV9jaGFubmVscyksCiAgICBkaW0gPSBkaW0oaW1hZ2VfYXJyYXkpCiAgKQoKICAjIFZpc3VhbGl6YXIgc2kgc2UgaGEgaW5kaWNhZG8KICBpZiAocGxvdCA9PSBUUlVFKXsKICBsYXlvdXQobWF0cml4KDE6MiwgMSwgMikpCiAgcGxvdChJbWFnZShvcmlnaW5hbF9pbWFnZSwgY29sb3Jtb2RlID0gIkNvbG9yIikpCiAgdGl0bGUoIk9yaWdpbmFsIikKICBwbG90KEltYWdlKChub2lzeV9pbWFnZV9hcnJheSksIGNvbG9ybW9kZSA9ICJDb2xvciIpKQogIHRpdGxlKHBhc3RlKCJSdWlkbzoiLCBub2lzZV90eXBlKSl9CiAgCiAgcmV0dXJuKG5vaXN5X2ltYWdlX2FycmF5KQp9CgoKIyBBcGxpY2FyIGxvcyBkaWZlcmVudGVzIHRpcG9zIGRlIHJ1aWRvIGEgY2FkYSBpbWFnZW4KYWRkX25vaXNlX3RvX2ltYWdlKCIxIiwgImdhdXNzaWFuIiwgbGlzdChzdGRfZGV2ID0gMC4zKSkKYWRkX25vaXNlX3RvX2ltYWdlKCIyIiwgInNpbnVzb2lkYWxfaGlnaCIsIGxpc3QoZnJlcXVlbmN5ID0gMjUsIGFtcGxpdHVkZSA9IDAuMikpCmFkZF9ub2lzZV90b19pbWFnZSgiMyIsICJzaW51c29pZGFsX2xvdyIsIGxpc3QoZnJlcXVlbmN5ID0gMiwgYW1wbGl0dWRlID0gMC4yKSkKYWRkX25vaXNlX3RvX2ltYWdlKCI0IiwgInNhbHRfcGVwcGVyIiwgbGlzdChlcHNpbG9uID0gMC4xKSkKYWRkX25vaXNlX3RvX2ltYWdlKCI1IiwgImdhbW1hIiwgbGlzdChsb29rcyA9IDIpKQphZGRfbm9pc2VfdG9faW1hZ2UoIjUiLCAidW5pZm9ybV9tdWx0aXBsaWNhdGl2ZSIsIGxpc3QobG9va3MgPSAyKSkKYGBgCgoKYGBge3J9CiMgUmVwcmVzZW50YWNpw7NuIGRlIGxhcyBkb3MgZmFtaWxpYXMgZGUgd2F2ZWxldHMgZW1wbGVhZGFzIHBvciBsYSBmdW5jacOzbiBpbXdkLgoKIyBHZW5lcmFjacOzbiBkZSB1biBmaWx0cm8gY29uIGxvcyBjb2VmaWNpZW50ZXMgY29ycmVzcG9uZGllbnRlcyBkZSBjYWRhIHdhdmVsZXQKd2F2ZV9jb2VmZnMgPC0gZmlsdGVyLnNlbGVjdChmaWx0ZXIubnVtYmVyID0gMTAsIGZhbWlseSA9ICJEYXViTGVBc3ltbSIpCndhdmVfY29lZmZzXzIgPC0gZmlsdGVyLnNlbGVjdChmaWx0ZXIubnVtYmVyID0gMTAsIGZhbWlseSA9ICJEYXViRXhQaGFzZSIpCgojIEVqZSB0ZW1wb3JhbAp4IDwtIHNlcV9hbG9uZyh3YXZlX2NvZWZmcyRIKQoKIyAjIFZpc3VhbGl6YWNpw7NuIGRlIGxhcyB3YXZlbGV0cwpwYXIobWZyb3cgPSBjKDEsIDIpKQoKcGxvdCh4LCB3YXZlX2NvZWZmcyRILCB0eXBlID0gImwiLCBsd2QgPSAyLAogICAgIG1haW4gPSAiV2F2ZWxldCBEYXViTGVBc3ltbSIsCiAgICAgeGxhYiA9ICJ4IiwgeWxhYiA9ICJBbXBsaXR1ZCIpCnBvaW50cyh4LCB3YXZlX2NvZWZmcyRILCBwY2ggPSAxOSkKCnBsb3QoeCwgd2F2ZV9jb2VmZnNfMiRILCB0eXBlID0gImwiLCBsd2QgPSAyLAogICAgIG1haW4gPSAiV2F2ZWxldCBEYXViRXhQaGFzZSIsCiAgICAgeGxhYiA9ICJ4IiwgeWxhYiA9ICJBbXBsaXR1ZCIpCnBvaW50cyh4LCB3YXZlX2NvZWZmc18yJEgsIHBjaCA9IDE5KQoKCnJtKHdhdmVfY29lZmZzKQpybSh3YXZlX2NvZWZmc18yKQpybSh4KQoKYGBgCgojIyBGdW5jacOzbiBpbXdkCgpFbCBwcmltZXIgbcOpdG9kbyBxdWUgZW1wbGVhcmVtb3MgcGFyYSBsYSBlbGltaW5hY2nDs24gZGUgcnVpZG8gZXMgZWwgYWxnb3JpdG1vIGRlIE1hbGxhdCBhIHRyYXbDqXMgZGUgCmxhIGZ1bmNpw7NuIGBpbXdkYC4gQW50ZXMgZGUgcG9kZXIgYXBsaWNhciBlc3RhIGZ1bmNpw7NuLCBuZWNlc3NpdGFtZXMgcmVhbGl6YXIgdW4gcHJlLXByb2Nlc2FtaWVudG8gYSBsYXMgaW3DoWdlbmVzLCB5YSBxdWUgCmVzdGUgYWxnb3JpdG1vIG5lY2VzaXRhIHVuYSBtYXRyaXogY3VhZHJhZGEgY3V5YXMgZGltZW5zaW9uZXMgc2VhbiBwb3RlbmNpYSBkZSBkb3MuIFNlIGhhbiBlc2NvZ2lkbyAyIG1hbmVyYXMgZGlzdGludGFzIHBhcmEgb2J0ZW5lciBpbcOhZ2VuZXMgY29uIGVsIHRhbWHDsW8gYWRlY3VhZG8uIFBvciB1biBsYWRvLCByZWRpbWVuc2lvbmFyZW1vcyBsYXMgaW3DoWdlbmVzIGNvbiBsYSBmdW5jacOzbiBgcmVzaXplYCwgbG8gcXVlIHBvZHLDrWEgY29ubGxldmFyIHByb2JsZW1hcyBkZSBkaXN0b3JzacOzbiBzaSBsYXMgaW3DoWdlbmVzIGVzdGFiYW4gbGVqb3MgZGUgdGVuZXIgZGltZW5zaW9uZXMgY3VhZHJhZGFzLiBQb3IgZWxsbywgdGFtYmnDqW4gdmFtb3MgYSBlbXBsZWFyIG90cmEgdMOpY25pY2EgeSByZWxsZW5hcmVtb3MgbGFzIG1hdHJpY2VzIGRlIGxhcyBpbcOhZ25lcyBjb24gdmFsb3JlcyBkZSAwIGhhc3RhIGFsY2FuemFyIGxhcyBkaW1lbnNpb25lcyBhZGVjdWFkYXMuIE9ic2VydmFtb3MgbG9zIHByb2JsZW1hcyBxdWUgc3VyZ3VlbiBlbiBjYWRhIGNhc28geSB0cmF0YXJlbW9zIGRlIHNvbHVjaW9uYXJsb3MgZGUgZGlzdGludGFzIG1hbmVyYXMuCgojIyMgUmVkaW1lbnNpb25hbmRvIGxhcyBpbcOhZ2VuZXMKCkNvbW8geWEgc2UgaGEgdmlzdG8gcGFyYSBwb2RlciBhcGxpY2FyIGxhIGZ1bmNpw7NuIGBpbXdkKClgZXMgbmVjZXNhcmlvIHBhcnRpciBkZSB1bmEgbWF0cml6IGN1YWRyYWRhIGN1eWFzIGRpbWVuc2lvbmVzIHNlYW4gcG90ZW5jaWEgZGUgZG9zLiBQb3IgZWxsbywgZW4gcHJpbWVyIGx1Z2FyIGNyZWFtb3MgdW5hIGZ1bmNpw7NuIGByZXNpemVfaW13ZCgpYCB0YWwgcXVlIGRhZGEgdW5hIGZvdG8gYnVzY2EgbGEgc3VibWF0cml6IGN1YWRyYWRhIHkgcG90ZW5jaWEgZGUgZG9zIG3DoXMgZ3JhbmRlIHBvc2libGUgeSBhIGNvbnRpbnVhY8OzbiByZWRpbWVuc2lvbmEgbGEgaW1hZ2VuIGEgZGljaGEgc3VibWF0cml6IGN1YWRyYWRhLgoKYGBge3J9CiMgUHJlLXByb2Nlc2FtaWVudG8gYWwgYXBsaWNhZG8gZGUgZnVuY2nDs24gaW13ZCAoYWxnb3JpdG1vIGRlIE1hbGxhdCkuCiMgUmVkaW1lbnNpb25hbWllbnRvIGRlIGxhIGltYWdlbiAKCnJlc2l6ZV9pbXdkPC0gZnVuY3Rpb24oZm90byl7CiAgCiAgaW1nIDwtIGFzLmNpbWcoZm90bykgIyBQYXNhciBhIGZvcm1hdG8gSW1hZ2VyIHBhcmEgYXBsaWNhciBmdW5jacOzbiByZXNpemUKICAgCiAgIyBEaW1lbnNpb25lcyBkZSBsYSBmb3RvCiAgZGltX2ZvdG8gPC0gZGltKGZvdG8pCiAgZmlsYXMgPC0gZGltX2ZvdG9bMV0KICBjb2x1bW5hcyA8LSBkaW1fZm90b1syXQogIAogIGxhZG9fbWluaW1vIDwtIG1pbihmaWxhcywgY29sdW1uYXMpICAjIFRhbWHDsW8gc3VibWF0cml6IGN1YWRyYWRhIG1hcyBncmFuZGUKICBsYWRvX3BvdGVuY2lhMiA8LSAyXmZsb29yKGxvZzIobGFkb19taW5pbW8pKSAjIFRhbWHDsW8gc3VibWF0cml6IGN1YWRyYWRhIHBvdGVuY2lhIGRlIGRvcyBtYXMgZ3JhbmRlCiAgCiAgZm90b19yZXNpemVkIDwtcmVzaXplKGZvdG8sIHcgPSBsYWRvX3BvdGVuY2lhMiwgaCA9IGxhZG9fcG90ZW5jaWEyKSAjIFJlZGltZW5zaW9uYWRvIGRlIGxhIGltYWdlbgoKcmV0dXJuKGZvdG9fcmVzaXplZCkKfQpgYGAKClBvciBvdHJvIGxhZG8sIGNyZWFtb3MgdW5hIGZ1bmNpw7NuIHBhcmEgZWwgcG9zdC1wcm9jZXNhbWllbnRvIGRlIGxhcyBpbcOhZ2VuZXMgdHJhcyBsYSBlbGltaW5pY2nDs24gZGUgcnVpZG8uIFF1ZXJlbW9zIGRldm9sdmVybGFzIGEgc3UgdGFtYcOxbyBvcmlnaW5hbCBjb24gZWwgb2JqZXRpdm8gZGUgY29tcGFyYXIgY29uIGxhcyBpbcOhZ2VuZXMgaW5pY2lhbGVzLgoKYGBge3J9CiMgUG9zdC1wcm9jZXNhbWllbnRvIGRlIGxhIGltYWdlbjoKIyBSZWRpbWVuc2lvbmFtaWVudG8gZGUgbGEgaW1hZ2VuIGEgc3UgdGFtYcOxbyBvcmlnaW5hbC4KCnJlc2l6ZV9pbXdkX3RvX29yaWdpbmFsPC0gZnVuY3Rpb24oaW1hZ2VfcmVkaW1lbnNpb25hZGEsIG5vbWJyZV9mb3RvKXsKICAKICAjaW1nIDwtIGFzLmNpbWcoaW1hZ2VfcmVkaW1lbnNpb25hZGEpCiAgZm90byA8LSBpbWFnZXNbW25vbWJyZV9mb3RvXV0KICAKICAjIERpbWVuc2lvbmVzIGRlIGxhIGZvdG8KICBkaW1fZm90byA8LSBkaW0oZm90bykKICBmaWxhcyA8LSBkaW1fZm90b1syXQogIGNvbHVtbmFzIDwtIGRpbV9mb3RvWzFdCiAgCiAgIyBSZWRpbWVuc2lvbmFkbyBkZSBsYSBpbWFnZW4KICBmb3RvX3Jlc2l6ZWQgPC1FQkltYWdlOjpyZXNpemUoaW1hZ2VfcmVkaW1lbnNpb25hZGEsIHcgPSBjb2x1bW5hcywgaCA9IGZpbGFzKSAKIAoKcmV0dXJuKGZvdG9fcmVzaXplZCkKfQpgYGAKCkNvbWVuemFtb3MgZ2VuZXJhbmRvIHVuYSBmdW5jacOzbiBgcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXRgIGNvbiBwYXLDoW1ldHJvcyBmb3RvLCB0aXBvIHkgcG9saWN5LiBFc3RhIGZ1bmNpw7NuIHJlYWxpemEgZW4gcHJpbWVyIGx1Z2FyIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0IGEgY2FkYSB1bm8gZGUgbG9zIHRyZXMgY2FuYWxlcyBkZSB1bmEgaW1hZ2VuLiBBIGNvbnRpbnVhY2nDs24sIHNlIHJlYWxpemEgZWwgdGhyZXNob2xkaW5nIGNvbiBsYSBmdW5jacOzbiB0aHJlc2hvbGQsIHB1ZGllbmRvIHZhcmlhciBkZSBlbCB0aXBvIGRlICJoYXJkIiBhICJzb2Z0IiB5IGVsIHBhcsOhbWV0cm8gcG9saWN5IChtb2RpZmljYW5kbyBhZGVjdWFkYW1lbnRlIGxvcyBwYXLDoW1ldHJvcyBuZWNlc2FyaW9zIGVuIGxhIGZ1bmNpw7NuIHRocmVzaG9sZCBlbiBlc3RlIMO6bHRpbW8gY2FzbykuIFVuYSB2ZXogcmVhbGl6YWRhIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbywgc2UgYXBsaWNhIGxhIHRyYXNuZm9ybWFkYSB3YXZlbGV0IGludmVyc2EgYGltd3JgIHBhcmEgcG9yIMO6bHRpbW8gcmVjb25zdHJ1aXIgbGEgaW1hZ2VuIGEgcGFydGlyIGRlIGxvcyB0cmVzIGNhbmFsZXMuCmBgYHtyfQpwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldCA8LSBmdW5jdGlvbihmb3RvLCB0aXBvID0gImhhcmQiLCBwb2xpY3kgPSAidW5pdmVyc2FsIikgewogICMgMS4gUmVhbGl6YW1vcyBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldCBhIGNhZGEgY2FuYWwKICBsd2QgPC0gbGFwcGx5KDE6MywgZnVuY3Rpb24oY2FuYWwpIHsKICAgIGltd2QoZm90b1ssLGNhbmFsXSkgIAogIH0pCiAgCiAgIyAyLiBBcGxpY2Ftb3MgZWwgdW1icmFsIGEgbG9zIGNvZWZpY2llbnRlcyBkZSBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldAogIGx3ZF90aHJlc2hvbGQgPC0gbGFwcGx5KGx3ZCwgZnVuY3Rpb24oY2FuYWxfd2QpIHsKICAgIG5pdmVsZXMgPC0gY2FuYWxfd2QkbmxldmVscwogICAgd2F2ZXRocmVzaDo6dGhyZXNob2xkKGNhbmFsX3dkLCBsZXZlbHMgPSAzOihuaXZlbGVzLTEpLCB0eXBlID0gdGlwbywgcG9saWN5ID0gcG9saWN5LGJ5X2xldmVsPVRSVUUsY29tcHJlc3Npb249RkFMU0UpCiAgfSkKICAjIDMuIEFwbGljYW1vcyBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldCBpbnZlcnNhIGEgY2FkYSBjYW5hbCB1bWJyYWxpemFkbwogIGlsd2QgPC0gbGFwcGx5KGx3ZF90aHJlc2hvbGQsIGZ1bmN0aW9uKGNhbmFsX3VtYnJhbGl6YWRvKSB7CiAgICB3YXZldGhyZXNoOjppbXdyKGNhbmFsX3VtYnJhbGl6YWRvKSAgIyBUcmFuc2Zvcm1hZGEgd2F2ZWxldCBpbnZlcnNhCiAgfSkKICAKICAjIDQuIFJlY29uc3RydWlyIGxhIGltYWdlbiBjb21iaW5hbmRvIGxvcyB0cmVzIGNhbmFsZXMgcHJvY2VzYWRvcwogIGltYWdlbl9yZWNvbnN0cnVpZGEgPC0gYWJpbmQ6OmFiaW5kKGlsd2RbWzFdXSwgaWx3ZFtbMl1dLCBpbHdkW1szXV0sIGFsb25nID0gMykKICAgIGltYWdlbiA8LSBJbWFnZShpbWFnZW5fcmVjb25zdHJ1aWRhLCBjb2xvcm1vZGUgPSAnQ29sb3InKQogIAogIHJldHVybihpbWFnZW4pCn0KYGBgCgoKUGFyYSBjb21lbnphciBlbCBhbsOhbGlzaXMsIHZhbW9zIGEgZW1wbGVhciBkb3MgaW3DoWdlbmVzIG11eSBwYXJlY2lkYXMgKGltw6FnZW5lcyA0IHkgNSkuIFVuYSBkZSBlbGxhcyB0aWVuZSBtdXkgYWx0YSByZXNvbHVjacOzbiBtaWVudHJhcyBxdWUgbGEgc2VndW5kYSBjdWVudGEgY29uIHVuYSBjYWxpZGFkIG11Y2hvIG1lbm9yLiBFbCBvYmpldGl2byBlcyBkZXRlcm1pbmFyIHNpIGxhIHJlc29sdWNpw7NuIGRlIGxhIGltYWdlbiBhZmVjdGEgYSBsYSBob3JhIGRlIGVsaW1pbmFyIHJ1aWRvIGRlIGVzdGEuIFZhbW9zIGEgcHJvYmFyIGNvbiBlbCBwcmltZXIgdGlwbyBkZSBydWlkbywgcnVpZG8gZ2F1c3NpYW5vLgoKYGBge3J9CiMgQcOxYWRpbW9zIHJ1aWRvIGdhdXNzaWFubyBhIGxhcyBpbcOhZ2VuZXMgY29uIHNkID0gMC4zCmltYWdlXzRfZ2F1c3NpYW5fbm9pc2UgPC0gYWRkX25vaXNlX3RvX2ltYWdlKCI0IiwgImdhdXNzaWFuIiwgbGlzdChzdGRfZGV2ID0gMC4zKSkKaW1hZ2VfNV9nYXVzc2lhbl9ub2lzZSA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjUiLCAiZ2F1c3NpYW4iLCBsaXN0KHN0ZF9kZXYgPSAwLjMpKQoKIyBIYWNlbW9zIHVuYSBsaXN0YSBjb24gbGFzIGltw6FnZW5lcyBjb24gcnVpZG8gYSByZWRpbWVuc2lvbmFyCmltYWdlc19mb3JfaW13ZF9jdXRfMSA8LSBsaXN0KGltYWdlXzRfZ2F1c3NpYW5fbm9pc2UsIGltYWdlXzVfZ2F1c3NpYW5fbm9pc2UpCm5hbWVzKGltYWdlc19mb3JfaW13ZF9jdXRfMSkgPC0gYygnNCBOb2lzZTogZ2F1c3NpYW4nLCAnNSBOb2lzZTogZ2F1c3NpYW4nKQoKIyBFbGltaW5hbW9zIHZhcmlhYmxlcyBpbm5lY2VzYXJpYXMKcm0oaW1hZ2VfNF9nYXVzc2lhbl9ub2lzZSkKcm0oaW1hZ2VfNV9nYXVzc2lhbl9ub2lzZSkKYGBgCgpBcGxpY2Ftb3MgbGEgZnVuY2nDs24gYHJlc2l6ZV9pbXdkYCBjcmVhZGEgYW50ZXJpb3JtZW50ZSBwYXJhIG9idGVuZXIgdW5hIG1hdHJpeiBjb24gbGFzIGRpbWVuc2lvbmVzIG5lY2VzYXJpYXMgcGFyYSBhcGxpY2FyIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0LgpgYGB7cn0KaW1hZ2VzX3JlY29ydGFkYXMgPC0gbGFwcGx5KGltYWdlc19mb3JfaW13ZF9jdXRfMSwgcmVzaXplX2ltd2QpCmBgYAoKVW5hIHZleiB0ZW5lbW9zIGxhcyBpbcOhZ2VuZXMgY29uIHJ1aWRvIGdlbmVyYWRhcyB5IHJlZGltZW5zaW9uYWRhcyBhZGVjdWFkYW1lbnRlLCBwb2RlbW9zIGFwbGljYXIgbGEgZnVuY2nDs24gYGltd2RgIGEgY2FkYSB1bm8gZGUgbG9zIHRyZXMgY2FuYWxlcyAoUiwgRyB5IEIpLiBFbXBsZWFtb3MgbGEgZnVuY2nDs24gYHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0YCBxdWUgZGV2dWVsdmUgbGFzIGltw6FnZW5lcyByZWNvbnN0cnVpZGFzIGRlc3B1w6lzIGRlbCB0aHJlc2hvbGRpbmcuIFBhcmEgcnVpZG8gZ2F1c3NpYW5vIHkgcGFyYSBjb21lbnphciwgdmFtb3MgYSBlbGVnaXIgbG9zIHBhcsOhbWV0cm9zIHBvciBkZWZlY3RvIGRlIGxhIGZ1bmNpw7NuIHBhcmEgZWwgdGhyZXNob2xkaW5nLgoKYGBge3J9CmltYWdlc19zaW5fcnVpZG9fZ2F1c3NpYW5vIDwtIGxhcHBseShpbWFnZXNfcmVjb3J0YWRhcywgcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXQpCmBgYApGaW5hbG1lbnRlLCB2aXN1YWxpemFtb3MgbG9zIHJlc3VsdGFkb3MuIFByaW1lcmFtZW50ZSwgb2JzZXJ2YW1vcyBsYXMgaW3DoWdlbmVzIHJlZGltZW5zaW9uYWRhcyBjb24gcnVpZG8geSBsYSBpbWFnZW4gb2J0ZW5pZGEgdHJhcyBlbCB1c28gZGVsIG3DqXRvZG8gZGUgdGhyZXNob2xkaW5nIHBhcmEgbGEgZWxpbWluYWNpw7NuIGRlIGVzdGUuIE9ic2VydmFtb3MgdW5hIHByaW5jaXBhbCBkaWZlcmVuY2lhIGVudHJlIGFtYmFzOiBsYSBmb3RvIHF1ZSBjb250YWJhIGNvbiBtZW5vciByZXNvbHVjacOzbiBwcmVzZW50YSB0YW1iacOpbiBlbCBwZW9yIHJlc3VsdGFkby4gQXVucXVlIGVsIHJ1aWRvIGhheWEgc2lkbyBlbGltaW5hZG9tLCBzdXMgYm9yZGVzIGVzdMOhbiBtw6FzIGRpZnVtaW5hZG9zIHkgdGllbmUgbXV5IGJhamEgY2FsaWRhZC4KCmBgYHtyfQpwYXIobWZyb3cgPSBjKDIsIDIpLCBjZXggPSAwLjUpCgpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDQgY29uIHJ1aWRvJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvX2dhdXNzaWFub1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiA0IHNpbiBydWlkbycpCgpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDUgY29uIHJ1aWRvJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvX2dhdXNzaWFub1tbMl1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiA1IHNpbiBydWlkbycpCmBgYApFbiBzZWd1bmRvIGx1Z2FyLCB2YW1vcyBhIHZpc3VhbGl6YXIgbGFzIGltw6FnZW5lcyBvcmlnaW5hbGVzIHkgbGFzIGltw6FnZW5lcyBzaW4gcnVpZG8gcmVkaW1lbnNpb25hZGFzIGEgc3UgdGFtYcOxbyBvcmlnaW5hbCwgdXNhbmRvIGxhIGZ1bmNpw7NuIGByZXNpemVfaW13ZF90b19vcmlnaW5hbGAuCgpgYGB7cn0KaW1hZ2VzX3Npbl9ydWlkbyA8LSBNYXAocmVzaXplX2ltd2RfdG9fb3JpZ2luYWwsIGltYWdlc19zaW5fcnVpZG9fZ2F1c3NpYW5vLCBjKCI0IiwgIjUiKSApCmBgYAoKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDIsIDIpLCBjZXggPSAwLjUpCgpkaXNwbGF5KEltYWdlKGltYWdlc1tbNF1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiA0IG9yaWdpbmFsJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDQgc2luIHJ1aWRvJykKCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzW1s1XV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDUgb3JpZ2luYWwnKQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9bWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpCnRpdGxlKCdJbWFnZW4gNSBzaW4gcnVpZG8nKQpgYGAKVmVtb3MgcXVlIGVmZWN0aXZhbWVudGUsIGxhIGltYWdlbiBxdWUgb3JpZ2luYWxlbWVudGUgY29udGFiYSBjb24gdW4gbsO6bWVybyBkZSBww614ZWxlcyBtdWNobyBtZW5vciwgbGEgaW1hZ2VuIDUsIHByZXNlbnRhIHVuYSBncmFuIGRpc3RvcnNpw7NuIHRyYXMgbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvLgoKYGBge3J9CnJtKGltYWdlc19mb3JfaW13ZF9jdXRfMSxpbWFnZXNfc2luX3J1aWRvX2dhdXNzaWFubykKYGBgCgoKQSBjb250aW51YWNpw7NuIHZhbW9zIGEgY29tcHJvYmFyIHF1ZSBlcyBsbyBxdWUgb2N1cnJlIGN1YW5kbyBhw7FhZGltb3MgcnVpZG8gc2ludMOpdGljbyBzaW51c29pZGFsIHkgc2kgbGEgZnJlY3VlbmNpYSBkZSBlc3RlIGFmZWN0YSBhbCByZXN1bHRhZG8gZGUgbGEgZWxpbWluYWNpw7NuIGRlIHJ1aWRvLiBUcmFiYWphcmVtb3MgY29uIHVuYSDDum5pY2EgZm90b2dyYWbDrWE6IGxhIGltYWdlbiAxLgoKYGBge3J9CiMgQcOxYWRpbW9zIHJ1aWRvIHNpbnVzb2lkYWwgYSBsYXMgaW3DoWdlbmVzIGNvbiBzZCA9IDAuMwppbWFnZV8xX3Npbm9zdWlkYWxfaGlnaCA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjEiLCAic2ludXNvaWRhbF9oaWdoIiwgbGlzdChmcmVxdWVuY3kgPSA1MCwgYW1wbGl0dWRlID0gMC4zKSkKaW1hZ2VfMV9zaW5vc3VpZGFsX2xvdyA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjIiLCAic2ludXNvaWRhbF9sb3ciLCBsaXN0KGZyZXF1ZW5jeSA9IDUsIGFtcGxpdHVkZSA9IDAuMykpCgojIEhhY2Vtb3MgdW5hIGxpc3RhIGNvbiBsYXMgaW3DoWdlbmVzIGNvbiBydWlkbyBhIHJlZGltZW5zaW9uYXIKaW1hZ2VzX2Zvcl9pbXdkX2N1dF8yIDwtIGxpc3QoaW1hZ2VfMV9zaW5vc3VpZGFsX2hpZ2ggLCBpbWFnZV8xX3Npbm9zdWlkYWxfbG93ICkKbmFtZXMoaW1hZ2VzX2Zvcl9pbXdkX2N1dF8yKSA8LSBjKCcxIE5vaXNlOiBzaW51c29pZGFsIGhpZ2gnLCAnMSBOb2lzZTogc2ludXNvaWRhbCBsb3cnKQoKIyBFbGltaW5hbW9zIHZhcmlhYmxlcyBpbm5lY2VzYXJpYXMKcm0oaW1hZ2VfMV9zaW5vc3VpZGFsX2hpZ2gpCnJtKGltYWdlXzFfc2lub3N1aWRhbF9sb3cpCmBgYAoKYGBge3J9CmltYWdlc19yZWNvcnRhZGFzIDwtIGxhcHBseShpbWFnZXNfZm9yX2ltd2RfY3V0XzIsIHJlc2l6ZV9pbXdkKQppbWFnZXNfc2luX3J1aWRvX3NpbnVzb2lkYWw8LSBsYXBwbHkoaW1hZ2VzX3JlY29ydGFkYXMsIHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0KQpgYGAKRXN0YSBjbGFybyBxdWUgbG9zIHBhcsOhbWV0cm9zIHBvciBkZWZlY3RvIGRlIGxhIGZ1bmNpw7NuIHRocmVzaG9sZCBubyBzb24gY2FwYWNlcyBkZSBlbGltaW5hciBlbCBydWlkbyBkZSB0aXBvIHNpbnVzb2lkYWwgZGUgbGEgbWFuZXJhIGVuIHF1ZSBzaSBsbyBlcmEgY29uIGVsIHJ1aWRvIGRlIHRpcG8gR2F1c3NpYW5vLCB1biB0aXBvIGRlIHJ1aWRvIGFsZWF0b3JpbywgYWwgY29udHJhcmlvIHF1ZSBlbCBzaW51c29pZGFsLCBxdWUgZXMgdW5hIHNlw7FhbCBwZXJpw7NkaWNhLgpgYGB7cn0KcGFyKG1mcm93ID0gYygyLCAyKSwgY2V4ID0gMC41KQoKZGlzcGxheShJbWFnZShpbWFnZXNfcmVjb3J0YWRhc1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAxIGNvbiBydWlkbyBkZSBmcmVjdWVuY2lhIGFsdGEnKQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9fc2ludXNvaWRhbFtbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAxIHNpbiBydWlkbycpCgpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDEgY29uIHJ1aWRvIGRlIGZyZWN1ZW5jaWEgYmFqYScpCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb19zaW51c29pZGFsW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDEgc2luIHJ1aWRvJykKYGBgClZhbW9zIGEgdmFyaWFyIHBhcsOhbWV0cm9zIGRlIGxhIGZ1bmNpw7NuIHRocmVzaG9sZCBwYXJhIGludGVudGFyIHF1aXRhciBlc3RlIHJ1aWRvIGRlIG1hbmVyYSBtw6FzIG1hbnVhbC4gRW4gcHJpbWVyIGx1Z2FyLCBjYW1iaWFtb3MgZWwgbsO6bWVybyBkZSBuaXZlbGVzIGFsIHF1ZSBhcGxpY2Ftb3MgZWwgdW1icmFsLCBwYXJhIGluY2x1aXJsb3MgYSB0b2Rvcy4gQ2FtYmlhbW9zIHBvbGljeSBhICJtYW51YWwiIHkgdmFyaWFtb3MgZWwgdmFsb3IgZGVsIHVtYnJhbCBkZSBmb3JtYSBtYW51YWwgaGFzdGEgZW5jb250cmFyIHVubyBxdWUgc2VhIHNhdGlzZmFjdG9yaW8uIApgYGB7cn0KcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXRfc2ludXNvaWRhbCA8LSBmdW5jdGlvbihmb3RvLCB0aXBvID0gImhhcmQiLCBwb2xpY3kgPSAidW5pdmVyc2FsIikgewogICMgMS4gUmVhbGl6YW1vcyBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldCBhIGNhZGEgY2FuYWwKICBsd2QgPC0gbGFwcGx5KDE6MywgZnVuY3Rpb24oY2FuYWwpIHsKICAgIGltd2QoZm90b1ssLGNhbmFsXSkgIAogIH0pCiAgCiAgIyAyLiBBcGxpY2Ftb3MgZWwgdW1icmFsIGEgbG9zIGNvZWZpY2llbnRlcyBkZSBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldAogIGx3ZF90aHJlc2hvbGQgPC0gbGFwcGx5KGx3ZCwgZnVuY3Rpb24oY2FuYWxfd2QpIHsKICAgIG5pdmVsZXMgPC0gY2FuYWxfd2QkbmxldmVscwogICAgd2F2ZXRocmVzaDo6dGhyZXNob2xkKGNhbmFsX3dkLCBsZXZlbHMgPSAxOihuaXZlbGVzLTEpLCB0eXBlID0gdGlwbywgcG9saWN5ID0gcG9saWN5LGJ5X2xldmVsPVRSVUUsY29tcHJlc3Npb249RkFMU0UsIHZhbHVlID0gNCkKICB9KQogICMgMy4gQXBsaWNhbW9zIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0IGludmVyc2EgYSBjYWRhIGNhbmFsIHVtYnJhbGl6YWRvCiAgaWx3ZCA8LSBsYXBwbHkobHdkX3RocmVzaG9sZCwgZnVuY3Rpb24oY2FuYWxfdW1icmFsaXphZG8pIHsKICAgIHdhdmV0aHJlc2g6Omltd3IoY2FuYWxfdW1icmFsaXphZG8pICAjIFRyYW5zZm9ybWFkYSB3YXZlbGV0IGludmVyc2EKICB9KQogIAogICMgNC4gUmVjb25zdHJ1aXIgbGEgaW1hZ2VuIGNvbWJpbmFuZG8gbG9zIHRyZXMgY2FuYWxlcyBwcm9jZXNhZG9zCiAgaW1hZ2VuX3JlY29uc3RydWlkYSA8LSBhYmluZDo6YWJpbmQoaWx3ZFtbMV1dLCBpbHdkW1syXV0sIGlsd2RbWzNdXSwgYWxvbmcgPSAzKQogICAgaW1hZ2VuIDwtIEltYWdlKGltYWdlbl9yZWNvbnN0cnVpZGEsIGNvbG9ybW9kZSA9ICdDb2xvcicpCiAgCiAgcmV0dXJuKGltYWdlbikKfQpgYGAKCgpgYGB7cn0KaW1hZ2VzX3Npbl9ydWlkb19zaW51c29pZGFsPC0gbGFwcGx5KGltYWdlc19yZWNvcnRhZGFzLCBwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldF9zaW51c29pZGFsLCB0aXBvID0ic29mdCIsIHBvbGljeSA9ICJtYW51YWwiKQpgYGAKCkNvbiB1biB2YWxvciBkZSB1bWJyYWwgZGUgNCB5IHZhcmlhbmRvIGVsIHRpcG8gYSAic29mdCIsIG9ic2VydmFtb3MgcXVlIGhlbW9zIGNvbnNlZ3VpZG8gZWxpbWluYXIgZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBhbHRhIGZyZWN1ZW5jaWEsIHBlcm8gcGFnYW5kbyB1biBwcmVjaW8gbXV5IGFsdG86IGxvcyBib3JkZXMgZGUgbGEgaW3DoWdlbiBzZSBkaXNvcnNpb25hbiBjb21wbGV0YW1lbnRlIHkgdGVuZW1vcyB1bmEgbXV5IGJhamEgcmVzb2x1Y2nDs24uIFBvciBvdHJvIGxhZG8sIGVsIHJ1aWRvIGRlIGZyZWN1ZW5jaWEgYmFqYSwgYXVucXVlIGhhIGRpc21pbnVpZG8sIGNsYXJhbWVudGUgc2lndWUgcHJlc2VudGUgZW4gbGEgaW1hZ2VuLiBFbCB1bWJyYWwgbmVjZXNhcmlvIHBhcmEgZWxpbWluYXJsbyBjb24gZXN0ZSBtw6l0b2RvIGVzIHRhbiBhbHRvIHF1ZSBkaXN0b3JzaW9uYXLDrWEgbGEgaW1hZ2VuIGNhc2kgcG9yIGNvbXBsZXRvLgpgYGB7cn0KcGFyKG1mcm93ID0gYygyLCAyKSwgY2V4ID0gMC41KQoKZGlzcGxheShJbWFnZShpbWFnZXNfcmVjb3J0YWRhc1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAxIGNvbiBydWlkbyBkZSBmcmVjdWVuY2lhIGFsdGEnKQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9fc2ludXNvaWRhbFtbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAxIHNpbiBydWlkbycpCgpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDEgY29uIHJ1aWRvIGRlIGZyZWN1ZW5jaWEgYmFqYScpCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb19zaW51c29pZGFsW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDEgc2luIHJ1aWRvJykKYGBgCgpQcm9iYW1vcyBvdHJvcyB0aXBvcyBkZSBydWlkbzogZ2FtbWEsIHNhbHQgYW5kIHBlcHBlciB5IHJ1aWRvIHVuaWZvcm1lIGVuIGxhcyBpbcOhZ2VuZXMgMiB5IDMuIApgYGB7cn0KIyBBw7FhZGltb3MgcnVpZG8gYSBpbcOhZ2VuZXMKaW1hZ2VfMl9zYWx0X3BlcHBlciA8LSBhZGRfbm9pc2VfdG9faW1hZ2UoIjIiLCAic2FsdF9wZXBwZXIiLCBsaXN0KGVwc2lsb24gPSAwLjEpKQppbWFnZV8zX2dhbW1hIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMyIsICJnYW1tYSIsIGxpc3QobG9va3MgPSAyKSkKaW1hZ2VfM191bmlmb3JtIDwtIGFkZF9ub2lzZV90b19pbWFnZSgiMyIsICJ1bmlmb3JtX211bHRpcGxpY2F0aXZlIiwgbGlzdChsb29rcyA9IDIpKQoKCiMgSGFjZW1vcyB1bmEgbGlzdGEgY29uIGxhcyBpbcOhZ2VuZXMgY29uIHJ1aWRvIGEgcmVjb3J0YXIKaW1hZ2VzX2Zvcl9pbXdkX2N1dF8zIDwtIGxpc3QoIGltYWdlXzJfc2FsdF9wZXBwZXIsIGltYWdlXzNfZ2FtbWEsIGltYWdlXzNfdW5pZm9ybSkKbmFtZXMoaW1hZ2VzX2Zvcl9pbXdkX2N1dF8zKSA8LSBjKCcyIE5vaXNlOiBzYWx0IGFuZCBwZXBwZXInLCAnMyBOb2lzZTogZ2FtbWEnLCczIE5vaXNlOiB1bmlmb3JtJykKCiMgRWxpbWluYW1vcyB2YXJpYWJsZXMgaW5uZWNlc2FyaWFzCnJtKGltYWdlXzJfc2FsdF9wZXBwZXIsIGltYWdlXzNfZ2FtbWEsIGltYWdlXzNfdW5pZm9ybSkKCmBgYApTdWd1aWVuZG8gZWwgbWlzbW8gcHJvY2VkaW1pZW50bywgcmVkaW1lbnNpb25hbW9zIGxhcyBpbcOhZ2VuZXMgeSB5IHJlYWxpemFtb3MgbGFzIHRyYW5zZm9ybWFkYXMgd2F2ZWxldCB5IGVsIHRocmVzaG9sZGluZyBjb24gbGEgZnVuY2nDs24gYHByb2Nlc2FyX2ltYWdlbl93YXZlbGV0YC4KYGBge3J9CmltYWdlc19yZWNvcnRhZGFzIDwtIGxhcHBseShpbWFnZXNfZm9yX2ltd2RfY3V0XzMsIHJlc2l6ZV9pbXdkKQppbWFnZXNfc2luX3J1aWRvczwtIGxhcHBseShpbWFnZXNfcmVjb3J0YWRhcywgcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXQpCmBgYApWaXN1YWxpemFtb3MgbG9zIHJlc3VsdGFkb3MuIFBhcmVjZSBxdWUgZWwgYWxnb3JpdG1vIGZ1bmNpb25hIGNvcnJlY3RhbWVudGUgcGFyYSBsb3MgdHJlcyB0aXBvcyBkZSBydWlkby4KYGBge3J9CnBhcihtZnJvdyA9IGMoMywgMiksIGNleCA9IDAuNSkKCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3JlY29ydGFkYXNbWzFdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpCnRpdGxlKCdJbWFnZW4gMiBjb24gc2FsdCBhbmQgcGVwcGVyJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvc1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAyIHNpbiBydWlkbycpCgpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1syXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDMgY29uIHJ1aWRvIGdhbW1hJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvc1tbMl1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAzIHNpbiBydWlkbycpCgpkaXNwbGF5KEltYWdlKGltYWdlc19yZWNvcnRhZGFzW1szXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDMgY29uIHJ1aWRvIHVuaWZvcm1lJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvc1tbM11dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAzIHNpbiBydWlkbycpCmBgYApSZWRpbWVuc2lvbmFtb3MgbGFzIGltw6FnZW5lcyBvYnRlbmlkYXMgYSBzdSB0YW1hw7FvIG9yaWdpbmFsIHBhcmEgcG9kZXIgY29tcGFyYXJsYXMgY29uIGVzdGFzLiBWZW1vcyBxdWUgZW4gZXN0ZSBjYXNvIGxhIGVsaW1pbmFjacOzbiBkZSBydWlkbyBoYSBzaWRvIG11eSBidWVuYSB5IG5vIGhheSBhcGVuYXMgZGlzdG9yc2nDs24gbmkgc3Vhdml6YWRvIGRlIGxvcyBib3JkZXMuCmBgYHtyfQppbWFnZXNfc2luX3J1aWRvIDwtIE1hcChyZXNpemVfaW13ZF90b19vcmlnaW5hbCwgaW1hZ2VzX3Npbl9ydWlkb3MsIGMoIjIiLCAiMyIsICIzIikgKQpgYGAKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDMsIDIpLCBjZXggPSAwLjUpCgpkaXNwbGF5KEltYWdlKGltYWdlc1tbMl1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAyIG9yaWdpbmFsJykKZGlzcGxheShJbWFnZShpbWFnZXNfc2luX3J1aWRvW1sxXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDIgc2luIHJ1aWRvJykKCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzW1szXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2Q9J3InKQp0aXRsZSgnSW1hZ2VuIDMgb3JpZ2luYWwnKQpkaXNwbGF5KEltYWdlKGltYWdlc19zaW5fcnVpZG9bWzJdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpCnRpdGxlKCdJbWFnZW4gMyBzaW4gcnVpZG8nKQoKZGlzcGxheShJbWFnZShpbWFnZXNbWzNdXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZD0ncicpCnRpdGxlKCdJbWFnZW4gMyBvcmlnaW5hbCcpCmRpc3BsYXkoSW1hZ2UoaW1hZ2VzX3Npbl9ydWlkb1tbM11dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kPSdyJykKdGl0bGUoJ0ltYWdlbiAzIHNpbiBydWlkbycpCmBgYApgYGB7cn0Kcm0oaW1hZ2VzX3JlY29ydGFkYXMsIGltYWdlc19zaW5fcnVpZG8sIGltYWdlc19zaW5fcnVpZG9zKQpgYGAKCgojIENvbmNsdXNpb25lcwo=
=======
LS0tCnRpdGxlOiAiRWxpbWluYWNpw7NuIGRlIHJ1aWRvIGVuIGltw6FnZW5lcyBjb24gd2F2ZWxldHMuIgpzdWJ0aXRsZTogIkFuw6FsaXNpcyBkZSBzZcOxYWxlcyIKYXV0aG9yOiAiR3J1cG8gRTogQWxlamFuZHJhIFZlbmVnYXMsIFJlYmVjYSBDb21wYW55LCBNYXJ0YSBNZWRpbmEsIEFsZWphbmRybyBDb3JuZWxpbyB5IElsaWEgWmhpZ2FyZXYuIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZWNobzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0aGVtZTogbHVtZW4KICAgIHRvYzogdHJ1ZQogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICBib29rZG93bjo6cGRmX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGZpZ3VyZV9jYXB0aW9uOiAiRmlndXJhIiAjIFJlZmVyZW5jaWFzIGVuIGNhc3RlbGxhbm8KICAgIHRhYmxlX2NhcHRpb246ICJUYWJsYSIKICBodG1sX25vdGVib29rOgogICAgZWNobzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0b2M6IHRydWUKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICBlY2hvOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB0cnVlCiAgICBmaWd1cmVfY2FwdGlvbjogIkZpZ3VyYSIKICAgIHRhYmxlX2NhcHRpb246ICJUYWJsYSIKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKcGFyYW1zOgogIGxhbmc6IEVTCmxhbmc6ICJgciBzd2l0Y2gocGFyYW1zJGxhbmcsIEVTID0gJ2VzLUVTJywgRU4gPSAnZW4tVVMnKWAiCmxhbmd1YWdlOgogIGxhYmVsOgogICAgZmlnOiAnRmlndXJhICcKICAgIHRhYjogJ1RhYmxhICcKICAgIGVxOiAnRWN1YWNpw7NuICcKICAgIHRobTogJ1Rlb3JlbWEgJwogICAgbGVtOiAnTGVtYSAnCiAgICBkZWY6ICdEZWZpbmljacOzbiAnCiAgICBjb3I6ICdDb3JvbGFyaW8gJwogICAgcHJwOiAnUHJvcG9zaWNpw7NuICcKICAgIGV4bTogJ0VqZW1wbG8gJwogICAgZXhyOiAnRWplcmNpY2lvICcKICAgIHByb29mOiAnRGVtb3N0cmFjacOzbi4gJwogICAgcmVtYXJrOiAnTm90YTogJwogICAgc29sdXRpb246ICdTb2x1Y2nDs24uICcKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpybShsaXN0ID0gbHMoKSkKYGBgCgojIEludHJvZHVjY2nDs24KCiMgRnVuZGFtZW50byB0ZcOzcmljbzogZWxpbWluYWNpw7NuIGRlIHJ1aWRvIGNvbiB3YXZlbGV0cwoKIyBGdW5jaW9uZXMgZGUgcHJvZ3JhbWFjaW9uIGVtcGxlYWRhcwoKIyBEZXNhcnJvbGxvIHkgcmVzdWx0YWRvcwoKQW50ZXMgZGUgY29tZW56YXIsIGluc3RhbGFtb3MgeS9vIGNhcmdhbW9zIHRvZG9zIGxvcyBwYXF1ZXRlcyByZXF1ZXJpZG9zLgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQpsaWJyYXJ5KHBhY21hbikKcF9sb2FkKGltYWdlciwgd2F2ZXRocmVzaCwgZ2dwbG90MiwgZHBseXIsIFNwYXRpYWxQYWNrLCB3YXZlc2xpbSwgRUJJbWFnZSwgc3RyaW5nciwganBlZykKYGBgCgpDb21lbnphbW9zIGNhcmdhbmRvIHkgdmlzdWFsaXphbmRvIGxhcyBmb3RvZ3JhZsOtYXMgYSBlbXBsZWFyLgpgYGB7cn0KaW1hZ2VzX3BhdGggPC0gbGlzdC5maWxlcygiLi9mb3RvcyIsIGZ1bGwubmFtZXMgPSBUUlVFKQoKbm9tYnJlc19pbWFnZXMgPC0gc3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gc3RyX3JlbW92ZV9hbGwoc3RyaW5nID0gaW1hZ2VzX3BhdGgsIHBhdHRlcm4gPSAiLi9mb3Rvcy8iKSwgcGF0dGVybiA9ICJcXC5KUEd8XFwuanBnIikKCmltYWdlcyA8LSBsYXBwbHkoaW1hZ2VzX3BhdGgsIHJlYWRKUEVHKSAjIENhcmdhbW9zIGxhcyBpbcOhZ2VuZXMKCiMgTm90YTogY2FtYmllIGRlIGxvYWQuaW1hZ2UgYSByZWFkSlBFRyBwcSBhc2kgeWEgbm8gaGFjZSBmYWx0YSB1c2FyIGVsIGRyb3AgcGFyYSBxdWl0YXIgbG8gZGUgY2ltZywgc2FsZSBiaWVuIGRpcmVjdGFtZW50ZQpuYW1lcyhpbWFnZXMpIDwtIG5vbWJyZXNfaW1hZ2VzCmBgYAoKYGBge3J9CiMgUm90YW1vcyBhbGd1bmFzIGRlIGxhcyBmb3RvcyBwYXJhIHVuYSB2aXN1YWxpemFjacOzbiBtw6FzIHVuaWZvcm1lCmZvdG9zX2FfZ2lyYXIgPC0gYygiMSIsICIyIiwgIjMiLCAiNCIpCmltYWdlc19yb3RhZGFzIDwtIGxhcHBseShpbWFnZXNbZm90b3NfYV9naXJhcl0sIGFwZXJtLCBwZXJtID0gYygyLCAxLCAzKSkKCgpmb3IgKGkgaW4gZm90b3NfYV9naXJhcikgewogIGltYWdlc19yb3RhZGFzW1tpXV0gPCBpbWFnZXNfcm90YWRhc1tbaV1dW2RpbShpbWFnZXNfcm90YWRhc1tbaV1dKVsxXToxLCAsIF0KfQoKaW1hZ2VzW2ZvdG9zX2FfZ2lyYXJdIDwtIGltYWdlc19yb3RhZGFzCnJtKGltYWdlc19yb3RhZGFzKQpybShmb3Rvc19hX2dpcmFyKQpgYGAKCgpgYGB7cn0KIyBWaXN1YWxpemFtb3MgbGFzIGltYWdlbmVzIG9yaWdpbmFsZXMKCnBhcihtZnJvdyA9IGMoMiwgMyksIG1hciA9IGMoMSwgMSwgMSwgMSkpCgpmb3IgKGltZyBpbiBub21icmVzX2ltYWdlcykgewogIGRpc3BsYXkoSW1hZ2UoaW1hZ2VzW1tpbWddXSwgY29sb3Jtb2RlID0gIkNvbG9yIiksIG1ldGhvZCA9ICJyIikKfQpgYGAKCiMjIEluY2x1c2nDs24gZGUgcnVpZG8gc2ludMOpdGljbyBlbiBsYXMgaW3DoWdlbmVzCgpgYGB7ciwgZmlnLmhlaWdodCA9IDR9CiMgRGVmaW5pY2nDs24gZGUgdGlwb3MgZGUgcnVpZG8KCk5PSVNFX1RZUEVTIDwtIGxpc3QoCiAgZ2F1c3NpYW4gPSBsaXN0KAogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7CiAgICAgICMgRGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlbCBydWlkbyBjb24gdW4gdmFsb3IgcHJlZGV0ZXJtaW5hZG8KICAgICAgbm9pc2Vfc3RkX2RldiA8LSBwYXJhbXMkc3RkX2RldiAlfHwlIDAuNQoKICAgICAgIyBHZW5lcmFjacOzbiBkZSBydWlkbyBnYXVzc2lhbm8KICAgICAgbm9pc2UgPC0gYXJyYXkoCiAgICAgICAgcm5vcm0obGVuZ3RoKGNoYW5uZWwpLCBtZWFuID0gMCwgc2QgPSBub2lzZV9zdGRfZGV2KSwKICAgICAgICBkaW0gPSBkaW0oY2hhbm5lbCkKICAgICAgKQoKICAgICAgIyBBc2VndXJhciBxdWUgbG9zIHZhbG9yZXMgZXN0w6luIGVudHJlIDAgeSAxCiAgICAgIHBtYXgoMCwgcG1pbigxLCBjaGFubmVsICsgbm9pc2UpKQogICAgfQogICksCiAgc2ludXNvaWRhbF9oaWdoID0gbGlzdCgKICAgIGdlbmVyYXRvciA9IGZ1bmN0aW9uKGNoYW5uZWwsIHBhcmFtcykgewogICAgICAjIEZyZWN1ZW5jaWEgeSBhbXBsaXR1ZCBkZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBhbHRhIGZyZWN1ZW5jaWEKICAgICAgZnJlcXVlbmN5IDwtIHBhcmFtcyRmcmVxdWVuY3kgJXx8JSAyNQogICAgICBhbXBsaXR1ZGUgPC0gcGFyYW1zJGFtcGxpdHVkZSAlfHwlIDAuMgoKICAgICAgIyBHZW5lcmFjacOzbiBkZSBydWlkbyBzaW51c29pZGFsCiAgICAgIGhlaWdodCA8LSBkaW0oY2hhbm5lbClbMV0KICAgICAgd2lkdGggPC0gZGltKGNoYW5uZWwpWzJdCiAgICAgIHggPC0gc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IHdpZHRoKQogICAgICB5IDwtIHNlcSgwLCAyICogcGksIGxlbmd0aC5vdXQgPSBoZWlnaHQpCiAgICAgIG5vaXNlX2dyaWQgPC0gb3V0ZXIoc2luKHggKiBmcmVxdWVuY3kpLCBzaW4oeSAqIGZyZXF1ZW5jeSkpCgogICAgICAjIEFwbGljYXIgZWwgcnVpZG8KICAgICAgbm9pc2UgPC0gYXJyYXkobm9pc2VfZ3JpZCAqIGFtcGxpdHVkZSwgZGltID0gZGltKGNoYW5uZWwpKQogICAgICBwbWF4KDAsIHBtaW4oMSwgY2hhbm5lbCArIG5vaXNlKSkKICAgIH0KICApLAogIHNpbnVzb2lkYWxfbG93ID0gbGlzdCgKICAgIGdlbmVyYXRvciA9IGZ1bmN0aW9uKGNoYW5uZWwsIHBhcmFtcykgewogICAgICAjIEZyZWN1ZW5jaWEgeSBhbXBsaXR1ZCBkZWwgcnVpZG8gc2ludXNvaWRhbCBkZSBiYWphIGZyZWN1ZW5jaWEKICAgICAgZnJlcXVlbmN5IDwtIHBhcmFtcyRmcmVxdWVuY3kgJXx8JSAyCiAgICAgIGFtcGxpdHVkZSA8LSBwYXJhbXMkYW1wbGl0dWRlICV8fCUgMC4yCgogICAgICAjIEdlbmVyYWNpw7NuIGRlIHJ1aWRvIHNpbnVzb2lkYWwKICAgICAgaGVpZ2h0IDwtIGRpbShjaGFubmVsKVsxXQogICAgICB3aWR0aCA8LSBkaW0oY2hhbm5lbClbMl0KICAgICAgeCA8LSBzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gd2lkdGgpCiAgICAgIHkgPC0gc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IGhlaWdodCkKICAgICAgbm9pc2VfZ3JpZCA8LSBvdXRlcihzaW4oeCAqIGZyZXF1ZW5jeSksIHNpbih5ICogZnJlcXVlbmN5KSkKCiAgICAgICMgQXBsaWNhciBlbCBydWlkbwogICAgICBub2lzZSA8LSBhcnJheShub2lzZV9ncmlkICogYW1wbGl0dWRlLCBkaW0gPSBkaW0oY2hhbm5lbCkpCiAgICAgIHBtYXgoMCwgcG1pbigxLCBjaGFubmVsICsgbm9pc2UpKQogICAgfQogICksCiAgc2FsdF9wZXBwZXIgPSBsaXN0KAogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7CiAgICAgICMgUHJvcG9yY2nDs24gZGUgcMOteGVsZXMgYWZlY3RhZG9zIHBvciBlbCBydWlkbyBkZSBzYWwgeSBwaW1pZW50YQogICAgICBlcHNpbG9uIDwtIHBhcmFtcyRlcHNpbG9uICV8fCUgMC4yCgogICAgICAjIEdlbmVyYWNpw7NuIGRlIHJ1aWRvCiAgICAgIG5vaXNlIDwtIG1hdHJpeChzYW1wbGUoYygwLCAxLCBOQSksIGxlbmd0aChjaGFubmVsKSwgcmVwbGFjZSA9IFRSVUUsIHByb2IgPSBjKGVwc2lsb24gLyAyLCBlcHNpbG9uIC8gMiwgMSAtIGVwc2lsb24pKSwKICAgICAgICBucm93ID0gZGltKGNoYW5uZWwpWzFdLCBuY29sID0gZGltKGNoYW5uZWwpWzJdCiAgICAgICkKICAgICAgY2hhbm5lbFshaXMubmEobm9pc2UpXSA8LSBub2lzZVshaXMubmEobm9pc2UpXQogICAgICBjaGFubmVsCiAgICB9CiAgKSwKICBnYW1tYSA9IGxpc3QoCiAgICBnZW5lcmF0b3IgPSBmdW5jdGlvbihjaGFubmVsLCBwYXJhbXMpIHsKICAgICAgIyBSdWlkbyBtdWx0aXBsaWNhdGl2byBnYW1tYSBjb24gcGFyw6FtZXRybyBkZSBkaXNwZXJzacOzbgogICAgICBsb29rcyA8LSBwYXJhbXMkbG9va3MgJXx8JSAyCiAgICAgIG5vaXNlIDwtIGFycmF5KHJnYW1tYShsZW5ndGgoY2hhbm5lbCksIHNoYXBlID0gbG9va3MsIHNjYWxlID0gMSAvIGxvb2tzKSwgZGltID0gZGltKGNoYW5uZWwpKQogICAgICBwbWF4KDAsIHBtaW4oMSwgY2hhbm5lbCAqIG5vaXNlKSkKICAgIH0KICApLAogIHVuaWZvcm1fbXVsdGlwbGljYXRpdmUgPSBsaXN0KAogICAgZ2VuZXJhdG9yID0gZnVuY3Rpb24oY2hhbm5lbCwgcGFyYW1zKSB7CiAgICAgICMgUnVpZG8gbXVsdGlwbGljYXRpdm8gdW5pZm9ybWUKICAgICAgbG9va3MgPC0gcGFyYW1zJGxvb2tzICV8fCUgMgogICAgICBub2lzZV9jaGFubmVsIDwtIFNwYXRpYWxQYWNrOjppbW5vaXNlKAogICAgICAgIGltZyA9IGNoYW5uZWwsCiAgICAgICAgdHlwZSA9ICJzcGVja2xlIiwKICAgICAgICBsb29rcyA9IGxvb2tzCiAgICAgICkKICAgICAgcG1heCgwLCBwbWluKDEsIG5vaXNlX2NoYW5uZWwpKQogICAgfQogICkKKQoKIyBGdW5jacOzbiBwYXJhIGHDsWFkaXIgcnVpZG8gYSB1bmEgaW1hZ2VuCmFkZF9ub2lzZV90b19pbWFnZSA8LSBmdW5jdGlvbihpbWFnZV9uYW1lLCBub2lzZV90eXBlLCBub2lzZV9wYXJhbXMgPSBsaXN0KCkscGxvdD1GQUxTRSkgewogICMgVmVyaWZpY2FyIHNpIGxhIGltYWdlbiBleGlzdGUgZW4gbGEgbGlzdGEKICBpZiAoIWltYWdlX25hbWUgJWluJSBuYW1lcyhpbWFnZXMpKSB7CiAgICBzdG9wKCJMYSBpbWFnZW4gY29uIGVzdGUgbm9tYnJlIG5vIHNlIGVuY3VlbnRyYSBlbiBsYSBsaXN0YSAnaW1hZ2VzJyIpCiAgfQoKICAjIFZlcmlmaWNhciBlbCB0aXBvIGRlIHJ1aWRvCiAgaWYgKCFub2lzZV90eXBlICVpbiUgbmFtZXMoTk9JU0VfVFlQRVMpKSB7CiAgICBzdG9wKAogICAgICAiRWwgdGlwbyBkZSBydWlkbyBlcyBkZXNjb25vY2lkby4gVGlwb3MgZGlzcG9uaWJsZXM6ICIsCiAgICAgIHBhc3RlKG5hbWVzKE5PSVNFX1RZUEVTKSwgY29sbGFwc2UgPSAiLCAiKQogICAgKQogIH0KCiAgIyBPYnRlbmVyIGxhIGltYWdlbiBvcmlnaW5hbCBkZSBsYSBsaXN0YQogIG9yaWdpbmFsX2ltYWdlIDwtIGltYWdlc1tbaW1hZ2VfbmFtZV1dCgogICMgQ29udmVydGlyIGxhIGltYWdlbiBhIHVuIGFycmF5IHNpIGVzIG5lY2VzYXJpbwogIGltYWdlX2FycmF5IDwtIGFzLmFycmF5KG9yaWdpbmFsX2ltYWdlKQoKICAjIEFwbGljYXIgcnVpZG8gYSBjYWRhIGNhbmFsCiAgbm9pc3lfY2hhbm5lbHMgPC0gbGFwcGx5KDE6MywgZnVuY3Rpb24oaSkgewogICAgY2hhbm5lbCA8LSBpbWFnZV9hcnJheVssICwgaV0KICAgIE5PSVNFX1RZUEVTW1tub2lzZV90eXBlXV0kZ2VuZXJhdG9yKGNoYW5uZWwsIG5vaXNlX3BhcmFtcykKICB9KQoKICAjIENyZWFyIGxhIGltYWdlbiBjb24gcnVpZG8KICBub2lzeV9pbWFnZV9hcnJheSA8LSBhcnJheSgKICAgIHVubGlzdChub2lzeV9jaGFubmVscyksCiAgICBkaW0gPSBkaW0oaW1hZ2VfYXJyYXkpCiAgKQoKICMgVmlzdWFsaXphciBzaSBzZSBoYSBpbmRpY2FkbwogIGlmIChwbG90ID09IFRSVUUpewogIGxheW91dChtYXRyaXgoMToyLCAxLCAyKSkKICBwbG90KEltYWdlKG9yaWdpbmFsX2ltYWdlLCBjb2xvcm1vZGUgPSAiQ29sb3IiKSkKICB0aXRsZSgiT3JpZ2luYWwiKQogIHBsb3QoSW1hZ2UoKG5vaXN5X2ltYWdlX2FycmF5KSwgY29sb3Jtb2RlID0gIkNvbG9yIikpCiAgdGl0bGUocGFzdGUoIlJ1aWRvOiIsIG5vaXNlX3R5cGUpKX0KICAKICByZXR1cm4obm9pc3lfaW1hZ2VfYXJyYXkpCn0KCmBgYAoKCmBgYHtyfQoKIyBBcGxpY2FyIGxvcyBkaWZlcmVudGVzIHRpcG9zIGRlIHJ1aWRvIGEgY2FkYSBpbWFnZW4KI2FkZF9ub2lzZV90b19pbWFnZSgiMSIsICJnYXVzc2lhbiIsIGxpc3Qoc3RkX2RldiA9IDAuMykpCiNhZGRfbm9pc2VfdG9faW1hZ2UoIjIiLCAic2ludXNvaWRhbF9oaWdoIiwgbGlzdChmcmVxdWVuY3kgPSAyNSwgYW1wbGl0dWRlID0gMC4yKSkKI2FkZF9ub2lzZV90b19pbWFnZSgiMyIsICJzaW51c29pZGFsX2xvdyIsIGxpc3QoZnJlcXVlbmN5ID0gMiwgYW1wbGl0dWRlID0gMC4yKSkKI2FkZF9ub2lzZV90b19pbWFnZSgiNCIsICJzYWx0X3BlcHBlciIsIGxpc3QoZXBzaWxvbiA9IDAuMSkpCiNhZGRfbm9pc2VfdG9faW1hZ2UoIjUiLCAiZ2FtbWEiLCBsaXN0KGxvb2tzID0gMikpCiNhZGRfbm9pc2VfdG9faW1hZ2UoIjUiLCAidW5pZm9ybV9tdWx0aXBsaWNhdGl2ZSIsIGxpc3QobG9va3MgPSAyKSkKYGBgCgoKIyMgRnVuY2nDs24gaW13ZAoKYGBge3IsZWNobz1GQUxTRX0KIyBhZ3JlZ2FyIHJ1aWRvIGEgaW1hZ2VuZXMKZ2F1c3NpYW5fbm9pc2VfNTwtYWRkX25vaXNlX3RvX2ltYWdlKCI1IiwgImdhdXNzaWFuIiwgbGlzdChzdGRfZGV2ID0gMC41KSkKZ2FtbWFfbm9pc2VfNTwtYWRkX25vaXNlX3RvX2ltYWdlKCI1IiwgImdhbW1hIiwgbGlzdChsb29rcyA9IDIpKQp1bmlmX25vaXNlXzU8LWFkZF9ub2lzZV90b19pbWFnZSgiNSIsICJ1bmlmb3JtX211bHRpcGxpY2F0aXZlIiwgbGlzdChsb29rcyA9IDIpKQpgYGAKCgoKYGBge3IsZWNobz1GQUxTRX0KI3Byb2JhciBjb24gMyAKaW1hZ2VuX25vaXNlIDwtIGxpc3QoZ2F1c3NpYW5fbm9pc2VfNSwgZ2FtbWFfbm9pc2VfNSwgdW5pZl9ub2lzZV81KQpuYW1lcyhpbWFnZW5fbm9pc2UpIDwtIGMoJzEgTm9pc2U6IGdhdXNzaWFuJywgJzIgTm9pc2U6IGdhbW1hJywgJzMgTm9pc2U6IHVuaWYnKQoKYGBgCgoKKipGdW5jacOzbiBwYXJhIGhhY2VyIGN1YWRyYWRhIGxhIGltYWdlbi4qKgoKUGFyYSBhcGxpY2FyIGVsIGFsZ29yaXRtbyBkZSBEZWZvcm1hY2nDs24gSXRlcmF0aXZhIGRlIE1hbGxhdCAoSU1XRCksIGVzIG5lY2VzYXJpbyBxdWUgbGEgaW1hZ2VuIHRlbmdhIHVuYSBmb3JtYSBjdWFkcmFkYS4gRGFkbyBxdWUgbXVjaGFzIGltw6FnZW5lcyBubyBzb24gY3VhZHJhZGFzLCBlcyBuZWNlc2FyaW8gY29udmVydGlybGFzIGFudGVzIGRlIGFwbGljYXIgZWwgYWxnb3JpdG1vLgoKQSBjb250aW51YWNpw7NuLCBzZSBwcmVzZW50YSB1bmEgZnVuY2nDs24gZGUgcHJlLXByb2Nlc2FtaWVudG8sIHF1ZSBhanVzdGEgY3VhbHF1aWVyIGltYWdlbiByZWN0YW5ndWxhciBhIHVuIHRhbWHDsW8gY3VhZHJhZG8sIG1hbnRlbmllbmRvIHN1cyBwcm9wb3JjaW9uZXMgb3JpZ2luYWxlcyBhbCBhZ3JlZ2FyIHJlbGxlbm8gKGZvbmRvIGJsYW5jbykgc2kgZXMgbmVjZXNhcmlvLiBFc3RhIHRyYW5zZm9ybWFjacOzbiBhc2VndXJhIHF1ZSBsYSBpbWFnZW4gc2VhIGNvbXBhdGlibGUgY29uIGVsIGFsZ29yaXRtbyBJTVdELgoKYGBge3J9CmhhY2VyX2N1YWRyYWRhX3BvdGVuY2lhXzIgPC0gZnVuY3Rpb24oaW1hZ2VuKSB7CiAgbl9maWxhcyA8LSBkaW0oaW1hZ2VuKVsxXQogIG5fY29sdW1uYXMgPC0gZGltKGltYWdlbilbMl0KICAKICBudWV2b190YW1hbm8gPC0gbWF4KG5fZmlsYXMsIG5fY29sdW1uYXMpCiAgCiAgc2lndWllbnRlX3BvdGVuY2lhXzIgPC0gMl5jZWlsaW5nKGxvZzIobnVldm9fdGFtYW5vKSkKICAKICBpbWFnZW5fY3VhZHJhZGEgPC0gYXJyYXkoMCwgZGltID0gYyhzaWd1aWVudGVfcG90ZW5jaWFfMiwgc2lndWllbnRlX3BvdGVuY2lhXzIsIGRpbShpbWFnZW4pWzNdKSkgCiAgCiAgaW1hZ2VuX2N1YWRyYWRhWzE6bl9maWxhcywgMTpuX2NvbHVtbmFzLCBdIDwtIGltYWdlbgogIAogIHJldHVybihpbWFnZW5fY3VhZHJhZGEpCn0KCmBgYAoKCkNhcmdhciBpbWFnZW5lcyBlbiBsYSBmdW5jacOzbiBjdWFkcmFkYQoKCgpBcGxpY2Ftb3MgbGEgZnVuY2nDs24gZW4gMyBmb3RvcwpgYGB7cn0KZm90b3NfY3VhZHJhZGFzIDwtIGxhcHBseShpbWFnZW5fbm9pc2UsIGhhY2VyX2N1YWRyYWRhX3BvdGVuY2lhXzIpCmBgYAoKSW1hZ2VuIE9yaWdpbmFsCgpgYGB7cixlY2hvPUZBTFNFfQppPTUKRUJJbWFnZTo6ZGlzcGxheShJbWFnZShpbWFnZXNbW2ldXSwgY29sb3Jtb2RlID0gJ0NvbG9yJyksIG1ldGhvZCA9ICdyJykKCmBgYAoKCgpgYGB7cixlY2hvPUZBTFNFfQpmb3IoaSBpbiBjKDE6MykpewpwcmludChuYW1lcyhmb3Rvc19jdWFkcmFkYXMpW2ldKQpwcmludCgnICcpCkVCSW1hZ2U6OmRpc3BsYXkoSW1hZ2UoZm90b3NfY3VhZHJhZGFzW1tpXV0sIGNvbG9ybW9kZSA9ICdDb2xvcicpLCBtZXRob2QgPSAncicpCn0KYGBgCgpDdWFuZG8gdXNvIGZvdG9zIGRlIG1heW9yIGNhbGlkYWQgc29sbyBhY2VwdGEgaGFzdGEgMiwgY29uIDMgYXBhcmVjZSBlc3RvOgpFcnJvcjogdmVjdG9yIG1lbW9yeSBsaW1pdCBvZiAxNi4wIEdiIHJlYWNoZWQsIHNlZSBtZW0ubWF4VlNpemUoKQoKCgpFbCB1bWJyYWwgInVuaXZlcnNhbCIsIHByb3B1ZXN0YSBwb3IgRG9ub2hvIHkgSm9obnN0b25lLCBlcyB1biBtw6l0b2RvIHV0aWxpemFkbyBlbiBsYSBlbGltaW5hY2nDs24gZGUgcnVpZG8gZW4gc2XDsWFsZXMgZSBpbcOhZ2VuZXMgbWVkaWFudGUgdHJhbnNmb3JtYWRhcyBkZSB3YXZlbGV0LiBFc3RhIGVzdHJhdGVnaWEgY2FsY3VsYSBlbCB1bWJyYWwgYXBsaWNhZG8gYSBsb3MgY29lZmljaWVudGVzIGRlIHdhdmVsZXQgZW4gZnVuY2nDs24gZGVsIHRhbWHDsW8gZGUgbGEgc2XDsWFsIHkgdW5hIGVzdGltYWNpw7NuIGRlbCBuaXZlbCBkZSBydWlkby4gTGEgZsOzcm11bGEgZGVsIHVtYnJhbCAidW5pdmVyc2FsIiBlcyAkJCBcc2lnbWEgXHNxcnR7MiBcbG9nIG59JCQgZG9uZGUgJFxzaWdtYSQgZXMgdW5hIGVzdGltYWNpw7NuIGRlbCBydWlkbyB5IG4gZXMgZWwgbsO6bWVybyBkZSBtdWVzdHJhcyBvIGVsZW1lbnRvcyBkZSBsYSBzZcOxYWwuCgoKCgpgYGB7cixlY2hvPUZBTFNFfQpwcm9jZXNhcl9pbWFnZW5fd2F2ZWxldCA8LSBmdW5jdGlvbihmb3RvLCB0aXBvID0gImhhcmQiLCBwb2xpY3kgPSAidW5pdmVyc2FsIikgewogIGx3ZCA8LSBsYXBwbHkoMTozLCBmdW5jdGlvbihjYW5hbCkgewogICAgd2F2ZXRocmVzaDo6aW13ZChmb3RvWywsY2FuYWxdKSAgCiAgfSkKICAKICAjIDIuIEFwbGljYW1vcyBlbCB1bWJyYWwgYSBsb3MgY29lZmljaWVudGVzIGRlIGxhIHRyYW5zZm9ybWFkYSB3YXZlbGV0CiAgbHdkX3RocmVzaG9sZCA8LSBsYXBwbHkobHdkLCBmdW5jdGlvbihjYW5hbF93ZCkgewogICAgbml2ZWxlcyA8LSBjYW5hbF93ZCRubGV2ZWxzCiAgICB3YXZldGhyZXNoOjp0aHJlc2hvbGQoY2FuYWxfd2QsIGxldmVscyA9IDM6KG5pdmVsZXMtMSksIHR5cGUgPSB0aXBvLCBwb2xpY3kgPSBwb2xpY3kpCiAgfSkKICAKICAjIDMuIEFwbGljYW1vcyBsYSB0cmFuc2Zvcm1hZGEgd2F2ZWxldCBpbnZlcnNhIGEgY2FkYSBjYW5hbCB1bWJyYWxpemFkbwogIGlsd2QgPC0gbGFwcGx5KGx3ZF90aHJlc2hvbGQsIGZ1bmN0aW9uKGNhbmFsX3VtYnJhbGl6YWRvKSB7CiAgICB3YXZldGhyZXNoOjppbXdyKGNhbmFsX3VtYnJhbGl6YWRvKSAgIyBUcmFuc2Zvcm1hZGEgd2F2ZWxldCBpbnZlcnNhCiAgfSkKICAKICAjIDQuIFJlY29uc3RydWlyIGxhIGltYWdlbiBjb21iaW5hbmRvIGxvcyB0cmVzIGNhbmFsZXMgcHJvY2VzYWRvcwogIGltYWdlbl9yZWNvbnN0cnVpZGEgPC0gYWJpbmQ6OmFiaW5kKGlsd2RbWzFdXSwgaWx3ZFtbMl1dLCBpbHdkW1szXV0sIGFsb25nID0gMykKICAKICAjIENvbnZlcnRpbW9zIGxhIGltYWdlbiByZWNvbnN0cnVpZGEgZW4gdW4gb2JqZXRvICdJbWFnZScgZGUgRUJJbWFnZQogIGltYWdlbiA8LSBJbWFnZShpbWFnZW5fcmVjb25zdHJ1aWRhLCBjb2xvcm1vZGUgPSAnQ29sb3InKQogIAogIHJldHVybihpbWFnZW4pCn0KCmBgYAoKKipJbWFnZW5lcyBzaW4gcnVpZG8qKgpgYGB7cixlY2hvPUZBTFNFfQojbWVtLm1heFZTaXplKDMyICogMTAyNF4zKSAKZm9yKGkgaW4gYygxOjMpKXsKcHJ1ZWJhPC1wcm9jZXNhcl9pbWFnZW5fd2F2ZWxldChmb3Rvc19jdWFkcmFkYXNbW2ldXSwgdGlwbyA9ICJoYXJkIiwgcG9saWN5ID0gInVuaXZlcnNhbCIpCnByaW50KG5hbWVzKGZvdG9zX2N1YWRyYWRhcylbaV0pCkVCSW1hZ2U6OmRpc3BsYXkocHJ1ZWJhLCBtZXRob2QgPSAncicsIHRpdGxlID0gJ0ltYWdlbiBXYXZlbGV0IHNpbiBydWlkbycpCn0KYGBgCgoKCgoKKlJ1aWRvIGdhdXNzaWFubyoKYGBge3IsZWNobz1GQUxTRX0KbGlicmFyeShtYWdpY2spCnBydWViYTwtcHJvY2VzYXJfaW1hZ2VuX3dhdmVsZXQoZm90b3NfY3VhZHJhZGFzW1sxXV0sIHRpcG8gPSAiaGFyZCIsIHBvbGljeSA9ICJ1bml2ZXJzYWwiKQppbWdfcmFzdGVyIDwtIGFzLnJhc3RlcihwcnVlYmEpCmltZ19tYWdpY2sgPC0gaW1hZ2VfcmVhZChpbWdfcmFzdGVyKQoKYGBgCgoKSW1hZ2VuIGNvbiBydWlkbyBHYXVzc2lhbm8gb3JpZ2luYWwKCjEuIEltYWdlbiBDb3J0YWRhIHNpbiBmaWx0cm8KCjIuIEFqdXN0YSBlbCBjb250cmFzdGUgZGUgbGEgaW1hZ2VuLCBpbmNyZW1lbnRhbmRvIGxhIGRpZmVyZW5jaWEgZW50cmUgbG9zIHDDrXhlbGVzIGNsYXJvcyB5IG9zY3Vyb3MuCgozLiBJbWFnZW4gY29uIGZpbHRybyBwYXNvIGFsdG8KCgpgYGB7cixlY2hvPUZBTFNFfQppbWFnZW5fY29ydGFkYTwtaW1hZ2VfY3JvcChpbWdfbWFnaWNrLCAiMTMwMHgxMjAwIikKCmtlcm5lbF9wYXNvX2FsdG88LW1hdHJpeChjKDAsLTEsMCwtMSw1LC0xLDAsLTEsMCksbnJvdz0zLG5jb2w9MykKCmZpbHRyYWRhX2FsdG88LWltYWdlX2NvbnZvbHZlKGltYWdlbl9jb3J0YWRhLGtlcm5lbF9wYXNvX2FsdG8pCgpjb25jb250cmFzdGU8LWltYWdlX2NvbnRyYXN0KGltYWdlbl9jb3J0YWRhKQoKaW1hZ2VuX2NvcnRhZGEgPC0gaW1hZ2VfYW5ub3RhdGUoaW1hZ2VuX2NvcnRhZGEsICJJbWFnZW4gQ29ydGFkYSIsIHNpemUgPSA1MCwgY29sb3IgPSAid2hpdGUiLCBsb2NhdGlvbiA9ICIrMTArMTAiKQpjb25jb250cmFzdGUgPC0gaW1hZ2VfYW5ub3RhdGUoY29uY29udHJhc3RlLCAiQ29uIENvbnRyYXN0ZSIsIHNpemUgPSA1MCwgY29sb3IgPSAid2hpdGUiLCBsb2NhdGlvbiA9ICIrMTArMTAiKQpmaWx0cmFkYV9hbHRvIDwtIGltYWdlX2Fubm90YXRlKGZpbHRyYWRhX2FsdG8sICJGaWx0cmFkYSBBbHRvIiwgc2l6ZSA9IDUwLCBjb2xvciA9ICJ3aGl0ZSIsIGxvY2F0aW9uID0gIisxMCsxMCIpCgojIENvbWJpbmFyIGxhcyBpbcOhZ2VuZXMgaG9yaXpvbnRhbG1lbnRlCmltYWdlbl9jb21iaW5hZGEgPC0gaW1hZ2VfYXBwZW5kKGMoaW1hZ2VuX2NvcnRhZGEsIGNvbmNvbnRyYXN0ZSwgZmlsdHJhZGFfYWx0bykpCgpFQkltYWdlOjpkaXNwbGF5KEltYWdlKGZvdG9zX2N1YWRyYWRhc1tbMV1dLCBjb2xvcm1vZGUgPSAnQ29sb3InKSwgbWV0aG9kID0gJ3InKQoKIyBNb3N0cmFyIGxhIGltYWdlbiBjb21iaW5hZGEKcHJpbnQoaW1hZ2VuX2NvbWJpbmFkYSkKYGBgCgoKCgojIyBGdW5jacOzbiBkZW5vaXNlLi4uCgojIENvbmNsdXNpb25lcwo=
>>>>>>> Alejandra